如何用Moq/Nunit模拟Marten.IDocumentSession的ToListAsync查询?
模拟IDocumentSession的ToListAsync扩展方法报错的解决办法
问题场景
要测试的业务代码:
IReadOnlyList<Student> students = await _session .Query<Student>() .Where(x => x.ClassId == classId) .ToListAsync(cancellationToken);
尝试的Mock代码:
private Mock<IDocumentSession> _sessionMock = new Mock<IDocumentSession>(); ... _sessionMock .Setup(x => x .Query<Students>() .Where(y => y.ClassId == classId) .ToListAsync(CancellationToken.None)) .ReturnsAsync(new List<Students>());
遇到的错误:
System.NotSupportedException : Unsupported expression: ... => ....ToListAsync(CancellationToken.None)
Extension methods (here: QueryableExtensions.ToListAsync) may not be used in setup / verification expressions.
可行解决办法
Moq无法直接模拟扩展方法(ToListAsync、Where都属于LINQ扩展方法),核心解决思路是:MockIDocumentSession的Query<T>基础方法,让它返回预先构造好的IQueryable<Student>集合,后续的LINQ操作会自动在这个内存集合上执行,无需单独Mock扩展方法。
具体实现步骤
- 准备测试用的学生数据集,可提前包含符合和不符合筛选条件的数据,用于验证逻辑
var testStudents = new List<Student> { new Student { ClassId = classId, Id = 1 }, new Student { ClassId = classId, Id = 2 }, new Student { ClassId = Guid.NewGuid(), Id = 3 } // 不符合条件的测试数据 }.AsQueryable();
- Mock
Query<Student>()方法,返回上述的IQueryable集合
_sessionMock .Setup(s => s.Query<Student>()) .Returns(testStudents);
- 执行测试并验证结果
此时业务代码调用Query<Student>().Where(...).ToListAsync()时,会先获取Mock的IQueryable集合,自动执行Where筛选,再通过ToListAsync返回符合条件的结果,完全模拟真实逻辑。
进阶:验证筛选条件(可选)
如果需要确保业务代码确实使用了正确的ClassId进行筛选,可以通过验证最终返回的结果是否符合预期,或者针对支持表达式参数的Query重载进行Mock:
_sessionMock .Setup(s => s.Query<Student>(It.IsAny<Expression<Func<Student, bool>>>())) .Returns((Expression<Func<Student, bool>> filter) => testStudents.Where(filter).AsQueryable());
这种方式可以确保传入的筛选表达式被正确应用,不过更多时候,通过验证最终返回的学生列表是否匹配预期,就能间接确认筛选逻辑的正确性。
注意事项
- 必须将测试集合转换为
IQueryable(调用AsQueryable()),因为IDocumentSession.Query<T>返回的是IQueryable<T>,这样后续的LINQ扩展方法才会走Queryable的实现,和真实场景一致。 - 如果业务代码包含更复杂的查询(如
Include、OrderBy),同样适用此思路:MockQuery<T>返回包含所需关联数据、排序后的IQueryable集合,后续扩展方法会自动在内存中执行。
内容的提问来源于stack exchange,提问作者Adil H. Raza
相关产品推荐
相关产品推荐

