You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Moq单元测试中处理多回调场景?

更优雅的解决方案:重构为AAA模式+调用结果收集

你当前的实现把断言逻辑嵌入到Mock的回调中,还需要手动维护回调计数器,确实会随着用例增多变得繁琐。可以通过收集所有调用结果+在执行完成后统一断言的方式优化,同时把调用次数的判断逻辑从测试用例中抽离,提升可维护性。

方案1:收集调用任务,执行后统一断言

核心思路是把所有被执行的ProcessTask收集到列表中,在macro.Execute()完成后,再根据业务参数判断要断言的目标任务(比如最后一次调用),同时验证调用次数是否符合预期:

[Test]
[TestCase("foo", false)]
[TestCase("foo", true)]
public void CommandLineShouldBeValidTest(string parameter1, bool parameter2)
{
    // Arrange
    var executedTasks = new List<ProcessTask>();
    var executorMock = new Mock<ITaskExecutor>();
    
    // 收集所有被执行的ProcessTask
    executorMock.Setup(m => m.Execute(It.IsAny<IExecutionContext>(), It.IsAny<ITask>()))
                .Callback<IExecutionContext, ITask>((_, task) =>
                {
                    if (task is ProcessTask processTask)
                    {
                        executedTasks.Add(processTask);
                    }
                });

    var macro = new Macro(parameter1, parameter2, executorMock.Object);
    
    // Act
    macro.Execute();
    
    // Assert
    // 1. 根据业务逻辑计算预期调用次数
    int expectedCallCount = parameter2 ? 3 : 2;
    // 验证调用次数是否符合预期
    executorMock.Verify(m => m.Execute(It.IsAny<IExecutionContext>(), It.IsAny<ITask>()), 
                        Times.Exactly(expectedCallCount));
    // 2. 断言指定次数的CommandLine(这里取最后一次,对应你原来的finalCallbackCounter)
    var targetTask = executedTasks[expectedCallCount - 1];
    Assert.AreEqual(expectedCommandLine, targetTask.CommandLine);
}

方案2:抽离调用次数计算逻辑(适合复杂场景)

如果业务逻辑中调用次数的判断规则越来越复杂,可以把这部分逻辑抽成独立方法,避免测试用例和断言代码变得臃肿:

[Test]
[TestCase("foo", false)]
[TestCase("foo", true)]
[TestCase("bar", false)] // 新增用例只需加参数,不用管次数
public void CommandLineShouldBeValidTest(string parameter1, bool parameter2)
{
    // Arrange
    var executedTasks = new List<ProcessTask>();
    var executorMock = new Mock<ITaskExecutor>();
    
    executorMock.Setup(m => m.Execute(It.IsAny<IExecutionContext>(), It.IsAny<ITask>()))
                .Callback<IExecutionContext, ITask>((_, task) =>
                {
                    if (task is ProcessTask processTask)
                    {
                        executedTasks.Add(processTask);
                    }
                });

    var macro = new Macro(parameter1, parameter2, executorMock.Object);
    
    // Act
    macro.Execute();
    
    // Assert
    int expectedCallCount = GetExpectedExecutionCount(parameter1, parameter2);
    executorMock.Verify(m => m.Execute(It.IsAny<IExecutionContext>(), It.IsAny<ITask>()), 
                        Times.Exactly(expectedCallCount));
    
    var targetTask = executedTasks.Last(); // 若总是断言最后一次,可直接用Last()
    Assert.AreEqual(expectedCommandLine, targetTask.CommandLine);
}

// 独立维护调用次数的业务规则
private int GetExpectedExecutionCount(string parameter1, bool parameter2)
{
    if (parameter2)
        return 3;
    if (parameter1 == "bar")
        return 1;
    return 2;
}

优化点说明

  1. 符合AAA测试模式:把断言逻辑移到Act步骤之后,测试结构更清晰,避免Mock回调中混杂业务断言。
  2. 减少硬编码参数:不用在TestCase中手动传递回调计数器,而是通过业务逻辑动态计算,新增用例时无需额外维护次数参数。
  3. 增强可维护性:调用次数的规则集中在一个方法里,后续业务变更只需修改这个方法,不用遍历所有测试用例。

内容的提问来源于stack exchange,提问作者Luis Angel Urena Lopez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:15:41