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

NSubstitute单元测试异步方法Mock首次调用不生效问题排查

解决方法

1. 改用NSubstitute的ReturnsAsync方法替代手动创建Task

手动通过Task.FromResult包装返回值可能引发异步状态异常,NSubstitute提供了专门的ReturnsAsync方法来处理异步场景,确保返回的Task状态正确:

_skuAdjustmentExclusionsRepositoryMock
    .GetExclusionReasonsForDateAsync(Arg.Any<IEnumerable<(int, int)>>(),
                                     Arg.Any<DateTime>(),
                                     Arg.Any<CancellationToken>())
    .ReturnsAsync(GetMockExclusionResults());

2. 检查参数类型是否完全匹配

重点排查第一个参数的元组类型:如果实际方法GetExclusionReasonsForDateAsync的第一个参数是命名元组(例如IEnumerable<(int StoreId, int Plu)>),而你Mock时用的是无名元组IEnumerable<(int, int)>,这两个属于不同类型,会导致参数匹配失败,Mock逻辑不触发,从而返回默认的空集合。

解决方式:将Mock的参数类型改为与实际方法一致,比如:

// 匹配命名元组参数
_skuAdjustmentExclusionsRepositoryMock
    .GetExclusionReasonsForDateAsync(Arg.Any<IEnumerable<(int StoreId, int Plu)>>(),
                                     Arg.Any<DateTime>(),
                                     Arg.Any<CancellationToken>())
    .ReturnsAsync(GetMockExclusionResults());

3. 把Mock实例移到测试方法内部,避免跨测试污染

当前Mock是类级别字段,若测试框架复用测试类实例(部分框架存在此情况),其他测试用例的Mock设置可能覆盖当前测试的配置。建议将Mock创建逻辑移到测试方法内:

[Fact]
public async Task When_Insert_Many_With_Result_Async_And_Exclusion_Checks_Specified_Then_Expected_Result_Returned()
{        
    // Arrange
    var skuAdjustmentExclusionsRepositoryMock = Substitute.For<ISkuAdjustmentExclusionsRepository>();
    // 配置Mock逻辑
    skuAdjustmentExclusionsRepositoryMock
        .GetExclusionReasonsForDateAsync(...)
        .ReturnsAsync(...);
    
    // 确保被测_repository使用这个新的Mock实例(比如通过构造函数注入)
    var repository = new YourRepository(skuAdjustmentExclusionsRepositoryMock);
    // ... 后续测试逻辑 ...
}

4. 验证Mock方法是否被正确调用

在Act步骤后添加验证逻辑,确认Mock方法确实被触发,排除参数不匹配的问题:

// Act
var result = await _repository.InsertManyWithResultAsync(movements, true, (false, false), CancellationToken.None);

// 验证Mock方法被调用1次
await _skuAdjustmentExclusionsRepositoryMock
    .Received(1)
    .GetExclusionReasonsForDateAsync(Arg.Any<IEnumerable<(int, int)>>(),
                                     Arg.Any<DateTime>(),
                                     Arg.Any<CancellationToken>());

如果验证失败(抛出ReceivedCallsException),说明参数不匹配,需要进一步核对实际调用的参数类型和值。

5. 确认返回值的类型兼容性

检查SkuExclusionResult是否正确继承/实现了ExclusionResult:

  • 若ExclusionResult是抽象类,SkuExclusionResult必须是其子类;
  • 若ExclusionResult是接口,SkuExclusionResult必须实现该接口。

如果类型不兼容,GetMockExclusionResults返回的集合会被隐式转换为空(或引发隐藏的转换异常),导致首次调用返回空集合。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:19:55