使用 Autofac.Moq 检查其他方法时,如何正确模拟方法到 return 特定数据?

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

有这样的 C# 代码并尝试检查方法 IsFailureProcessStatus 是否 return 为真。 来自 dapper class SqlMapper 的查询方法,它使用参数调用存储过程。

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;
       }
}

单元测试代码位于下方:

    [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;
        }
    }

我尝试在调用 sut.IsFailureProcessStatus 代码时将 GetProcessStatusIds 方法设置为 return 值,但是在调试它的 运行 GetProcessStatusIds 并在尝试调用 Query 方法时抛出 NullReferenceException 异常。

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.

我在模拟这个方法时到底做错了什么?我怎么能说测试不要 运行 这个 GetProcessStatusIds 而只是 return 硬编码值?

试过这个,但它对我不起作用:

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);
}

做单元测试的第一件事就是定义测试的目标,在你的问题中你试图测试IsFailureProcessStatusinside逻辑,这里的问题是您模拟了具有 IsFailureProcessStatus 方法的接口 IDatabaseManager

你不需要那个模拟,当它用作其他客户服务的供应商时,你只需要模拟 IDatabaseManager

并且因为您正在测试 IsFailureProcessStatus 的内部逻辑,所以您只需要模拟和设置执行内部逻辑所需的方法,例如 IDbConnectionsProvider 并设置其方法 CoreDbProcessesConnection 使其可用于 DatabaseManager 真实实例。

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);

我能理解你为什么错误地尝试模拟 GetProcessStatusIds,但不需要它,因为我们有 DatabaseManager 的真实实例,所以你只会模拟需要的依赖接口在GetProcessStatusIds的执行过程中,所以我们这里不需要设置HrReportDbConnection