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
相关产品推荐
相关产品推荐

