Search code examples
c#unit-testingmoqautofacdapper

How to mock method properly to return specific data when checking other method with Autofac.Moq?


Have such C# code and try to check method IsFailureProcessStatus to return true. Query method from dapper class SqlMapper which call stored procedures with parameters.

public class DatabaseManager : IDatabaseManager
{
        private readonly SqlConnection CoreDbProcessesConnection;
        private readonly SqlConnection HrReportDbConnection;
        // there are other private fields with parameters and sql-procedures names

        public DatabaseManager(IDbConnectionsProvider dbConnectionsProvider)
        {
            this.CoreDbProcessesConnection = dbConnectionsProvider.CoreDbProcessesConnection;
            this.HrReportDbConnection = dbConnectionsProvider.HrReportDbConnection;
        }

        public List<CoreProcessStatusDto> GetProcessStatusIds(string ProcessName, DateTime dateTime)
        {
            var parameters = new DynamicParameters();
            parameters.Add(processStatusProcedureParamName01, ProcessName);
            parameters.Add(processStatusProcedureParamName02, dateTime);

            var output = this.CoreDbProcessesConnection
                .Query<CoreProcessStatusDto>(ProcessStatusProcedureName, parameters, commandType: CommandType.StoredProcedure).ToList();

            return output;
        }

        public bool IsFailureProcessStatus(StepDto.StepDescription step, DateTime dateTime)
        {
            bool isStepFailure = true;

            Stopwatch doStepUntil = new Stopwatch();
            doStepUntil.Start();

            while (doStepUntil.Elapsed < TimeSpan.FromSeconds(step.SecondsElapsed))
            {

                step.StatusTypesList = this.GetProcessStatusIds(step.ProcessName, dateTime);
                var statusTypesStepSelection = step.StatusTypesList.Select(st => st.ProcessStatusTypeId).ToList();

                //...
                // if...else operations here to make step true or false
                //...
            }

            doStepUntil.Stop();

            return isStepFailure;
       }
}

Unit test code is located below:

    [TestClass]
    public class DatabaseManagerTests
    {
       [TestMethod]
        public void IsFailureProcessStatus_ReturnTrue()
        {
            DateTime dateTime = DateTime.Now;

            StepDto step1Dto = new StepDto()
            {
                JobName = "ETL - HR - FilesImport - Reporting",
                JobStepName = "RunMCP_User_Department_Map",
                Step = new StepDto.StepDescription()
                {
                    StatusTypesList = new List<CoreProcessStatusDto>(),
                    ProcessName = "HR_User_Department_Map_Import",
                    SecondsElapsed = 30,
                    PackageCount = 2
                }
            };

            using (var mock = AutoMock.GetLoose())
            {
                var dbProviderMock = new Mock<IDbConnectionsProvider>(MockBehavior.Loose);

                var dbMock = new Mock<DatabaseManager>(dbProviderMock.Object);

                mock.Mock<IDatabaseManager>()
                    .Setup(p => p.GetProcessStatusIds(step1Dto.Step.ProcessName, dateTime))
                    .Returns(GetCoreProcessesStatusIdsTest());

                var sut = mock.Provide(dbMock.Object);

                //var sut = mock.Create<DatabaseManager>();

                var actual = sut.IsFailureProcessStatus(step1Dto.Step, dateTime);

                Assert.IsTrue(actual);
            }
        }

        private List<CoreProcessStatusDto> GetCoreProcessesStatusIdsTest()
        {
            var output = new List<CoreProcessStatusDto>()
            {
                new CoreProcessStatusDto() { ProcessStatusTypeId = 3 },
                new CoreProcessStatusDto() { ProcessStatusTypeId = 2 }
            };

            return output;
        }
    }

I tried to setup GetProcessStatusIds method to return values when calling sut.IsFailureProcessStatus code, but while debug its run GetProcessStatusIds and throw NullReferenceException exception when try calling Query method.

Test Name:  IsFailureProcessStatus_ReturnTrue
Test Outcome:   Failed
Result StackTrace:  
at Dapper.SqlMapper.<QueryImpl>d__140`1.MoveNext() in C:\projects\dapper\Dapper\SqlMapper.cs:line 1066
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in C:\projects\dapper\Dapper\SqlMapper.cs:line 721
   at ATP.HR.FolderWatcher.Service.Managers.DatabaseManager.GetProcessStatusIds(String ProcessName, DateTime dateTime) in C:\HOME\anatolii.dmitryv\src\HRM\hr-folder-watcher-service\ATP.HR.FolderWatcher.Service\Managers\DatabaseManager.cs:line 46
   at ATP.HR.FolderWatcher.Service.Managers.DatabaseManager.IsFailureProcessStatus(StepDescription step, DateTime dateTime) in C:\HOME\anatolii.dmitryv\src\HRM\hr-folder-watcher-service\ATP.HR.FolderWatcher.Service\Managers\DatabaseManager.cs:line 83
   at ATP.HR.FolderWatcher.Service.Test.DatabaseManagerTests.IsFailureProcessStatus_ReturnTrue() in C:\HOME\anatolii.dmitryv\src\HRM\hr-folder-watcher-service\ATP.HR.FolderWatcher.Service.Tests\DatabaseManagerTests.cs:line 57
Result Message: 
Test method ATP.HR.FolderWatcher.Service.Test.DatabaseManagerTests.IsFailureProcessStatus_ReturnTrue threw exception: 
System.NullReferenceException: Object reference not set to an instance of an object.

And what exactly I do wrong in mock of this method? How I can say to test do not run this GetProcessStatusIds and just return hardcoded values?

tried this using but it didnt work for me:

using (var mock = AutoMock.GetLoose())
{

      mock.Mock<IDatabaseManager>()
          .Setup(p => p.GetProcessStatusIds(It.IsAny<string>(), It.IsAny<DateTime>()))
          .Returns(GetCoreProcessesStatusIdsTest());

          var sut = mock.Create<DatabaseManager>();

          var actual = sut.IsFailureProcessStatus(step1Dto.Step, dateTime);

          Assert.IsTrue(actual);
}

Solution

  • The first thing when doing unit testing is to define the goal of the test, in your question you are trying to test the inside logic of IsFailureProcessStatus, the problem here is that you mocking the interface IDatabaseManager that has the IsFailureProcessStatus method.

    You don't need that mocking, you will only will need to mock IDatabaseManager when it uses as supplier for other clients services.

    And because you are testing the inside logic of IsFailureProcessStatus you will only need to mock and setup method that are needed for the execution of the inside logic like IDbConnectionsProvider and setup its method of CoreDbProcessesConnection for it to be available to the DatabaseManager real instance.

    var dbProviderMock = new Mock<IDbConnectionsProvider>(MockBehavior.Loose);
    
    dbProviderMock
        .Setup(p => p.CoreDbProcessesConnection)
        .Returns(new SqlConnection(...));
    
    DatabaseManager databaseManager = new DatabaseManager(dbProviderMock.Object);
    
    var actual = databaseManager.IsFailureProcessStatus(step1Dto.Step, dateTime);
    
    Assert.IsTrue(actual);
    

    I can understand why you wrongly tried to mock GetProcessStatusIds, but it won't needed since we have the real instance of DatabaseManager, so you will only be mocking depended interfaces that are needed in the execution process of GetProcessStatusIds, thats why we does not need here the setup of HrReportDbConnection.