使用NSubstitute模拟Mongo IFindFluent接口失败,求解决方案
问题根源与解决办法
这个问题我之前也碰到过,核心原因是你试图模拟的FirstOrDefaultAsync并不是IFindFluent接口的实例方法,而是MongoDB.Driver库提供的静态扩展方法。像NSubstitute这类模拟框架只能拦截和模拟接口/类的实例成员,静态扩展方法不在它们的处理范围内,所以直接给ff.FirstOrDefaultAsync().Returns("asd")肯定会报错。
解决思路
既然扩展方法FirstOrDefaultAsync的内部逻辑是先调用IFindFluent.Limit(1)获取一个限制数量的查询实例,再调用ToListAsync()拿到结果列表,最后取第一个元素,那我们只需要模拟这个流程里的实例方法就行:
- 先模拟调用
Limit(1)后返回的IFindFluent实例,并让它的ToListAsync()返回包含目标值的列表; - 让初始的
IFindFluent实例调用Limit(1)时返回上面模拟好的实例; - 这样当你调用
FirstOrDefaultAsync时,它会顺着我们模拟的流程走,最终返回预期值。
修正后的测试代码
[Fact] public async Task Test() { // 模拟Limit(1)后的查询实例 var limitedFindFluent = Substitute.For<IFindFluent<string, string>>(); // 设置ToListAsync返回包含目标字符串的列表 limitedFindFluent.ToListAsync().ReturnsAsync(new List<string> { "asd" }); // 模拟初始的IFindFluent实例 var findFluent = Substitute.For<IFindFluent<string, string>>(); // 让Limit(1)返回我们预先模拟好的实例 findFluent.Limit(1).Returns(limitedFindFluent); // 执行测试并断言结果 var result = await findFluent.FirstOrDefaultAsync(); Assert.Equal("asd", result); }
额外提醒
- 尽量用
async/await来处理异步测试,避免直接使用.Result,这样能避免潜在的死锁问题; - 如果你要模拟其他类似的MongoDB扩展方法(比如
SingleAsync、CountAsync等),都可以用同样的思路:找到扩展方法内部依赖的实例方法,然后依次模拟这些实例方法的返回值。
内容的提问来源于stack exchange,提问作者strongwind
相关产品推荐
相关产品推荐

