【问题标题】:How to mock method properly to return specific data when checking other method with Autofac.Moq?使用 Autofac.Moq 检查其他方法时如何正确模拟方法以返回特定数据?
【发布时间】:2019-11-22 15:47:42
【问题描述】:

有这样的 C# 代码并尝试检查方法 IsFailureProcessStatus 是否返回 true。 dapper 类 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;
        }
    }

我尝试设置 GetProcessStatusIds 方法以在调用 sut.IsFailureProcessStatus 代码时返回值,但在调试其运行 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 而只返回硬编码值?

试过了,但对我没用:

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

【问题讨论】:

  • 我不确定,您可以尝试用 lambda 替换 GetCoreProcessesStatusIdsTest() 吗? () => 获取CoreProcessesStatusIdsTest()。或者尝试说 sut = new Mock 并为模拟进行设置。然后调用 sut.Object.IsFailureProcessStatus(...)
  • @GlennvanAcker lambda 对我不起作用。设置后使用 sut.Object 它总是返回 false 并且在调试时永远不会进入方法。
  • 您是否尝试过模拟 IDatabaseManager,而不是 DatabaseManager。我认为这可能与它有关。我还认为在为接口设置模拟之后使用 Mock.Create 创建模拟可能会产生不良结果。 (不确定这一点,因为我找不到好的文档)
  • 看看这里的例子:autofaccn.readthedocs.io/en/latest/integration/moq.html 这里他们使用 Automock.CreateLoose 在构造函数中注入模拟依赖。但他们在模拟实际对象,而不是界面。

标签: c# unit-testing moq autofac dapper


【解决方案1】:

进行单元测试的第一件事是定义测试的目标,在你的问题中你试图测试IsFailureProcessStatus内部逻辑,这里的问题是你在嘲笑具有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 的真实实例,所以你只会模拟@ 执行过程中需要的依赖接口987654332@,所以这里不需要设置HrReportDbConnection

【讨论】:

  • 是的,你是对的。在我的例子中,我创建了新的 DataAccess 类并通过 IDataAccess 模拟(并在 DatabaseManager 构造函数中添加了它)。在这个新类中,我移动了 GetProcessStatusIds 方法和其他使用 IDbConnectionsProvider 连接到数据库的方法......所以我可以轻松地为 DatabaseManager 创建 sut 并且测试现在很好
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 2018-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多