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

NSubstitute.Received(1)偶现误报多次调用,如何排查原因?

问题:NSubstitute Received(1) 偶尔提示方法被调用两次,但实际确认仅调用一次

我使用NSubstitute编写测试用例,验证业务逻辑中方法的调用次数,测试代码如下:

await _memberAddUpdateService.Received(1).UpdateMemberNameAsync(member);
await _customerAddUpdateService.DidNotReceive().AddCustomerAsync(member.Customer!);
await _customerAddUpdateService.Received(1).UpdateCustomerAsync(member.Customer!);
await _customerAddUpdateService.DidNotReceive().UpdateCustomerAsync(member.RfmsCustomer);

这些检查大多数时候正常,但偶尔会失败,提示相关方法被调用了两次。我确认项目中这些方法仅被调用一次,也不存在导致重复调用的循环逻辑。想知道是NSubstitute的Received调用存在异常,还是代码设计方面的因素导致方法被多次调用?


分析与排查建议

一、先排除NSubstitute相关的常见问题

  1. 测试隔离不足
    如果测试类中的Substitute实例是类级别的字段,且未在每个测试方法执行前重新初始化(比如未在[SetUp]/[TestInitialize]中创建新实例),会导致前一次测试的调用记录被累积到下一次测试中,从而触发调用次数不符的错误。确保每个测试都使用全新的Substitute实例。

  2. 异步方法的等待时机问题
    若被测试逻辑中存在未正确await的异步操作(比如fire-and-forget的Task.Run、未await的方法调用),可能导致测试代码提前执行Received检查,而实际方法在检查完成后才完成第二次调用(比如异步任务延迟执行)。需确认被测试代码中所有异步调用都已正确await。

  3. 参数匹配的隐性问题
    NSubstitute的参数匹配是基于引用或值相等性的。如果实际调用时传入的参数与测试中member.Customer!不是同一个实例(比如业务逻辑中克隆了对象、重新实例化了Customer),可能导致NSubstitute误判调用次数:比如实际一次调用未匹配到测试中的参数,而其他无关调用被错误匹配,或者反过来,两次不同参数的调用被宽松匹配(比如用了Arg.Any<Customer>)算成了同一方法的调用。

二、代码设计/执行的隐藏因素

  1. 重复的事件订阅
    若业务逻辑中存在事件订阅逻辑,且未正确避免重复订阅(比如在初始化方法中多次注册同一事件处理程序),会导致事件触发时方法被调用多次。检查是否存在重复订阅的场景。

  2. 并发/多线程触发
    业务逻辑若涉及并发操作(比如多线程、Parallel类、异步流的并发处理),可能在测试时因线程调度的随机性,导致方法被多次调用。这种情况下调用次数的异常是偶发的,与线程执行顺序有关。

  3. 依赖注入的生命周期问题
    如果被测试服务是单例生命周期,且测试环境未隔离单例实例(比如多个测试共享同一个服务实例),会导致调用次数在测试间累积,引发偶发的计数错误。

三、排查步骤

  1. 添加调用日志
    在Substitute上添加回调,记录每次方法调用的细节,当测试失败时查看日志确认是否真的被调用两次:

    _customerAddUpdateService.When(x => x.UpdateCustomerAsync(Arg.Any<Customer>()))
                             .Do(ctx => 
                                 Console.WriteLine($"调用时间: {DateTime.Now}, 参数: {JsonConvert.SerializeObject(ctx.Arg<Customer>())}, 调用栈: {Environment.StackTrace}")
                             );
    
  2. 调试验证调用次数
    在被测试方法的入口处打条件断点,统计实际调用次数,确认是否真的存在两次调用。

  3. 强化测试隔离
    确保每个测试方法执行前,所有依赖的Substitute和服务实例都被重新初始化,避免跨测试的状态污染。

内容的提问来源于stack exchange,提问作者Lotzi11

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:23:15