使用NSubstitute测试EF仓储异步查询遇IAsyncQueryProvider异常求助
问题解决:Mock DbContext异步查询测试异常及返回null问题
核心问题梳理
你遇到的两个核心问题:
- 使用内存数据库或Mock DbContext时,异步查询(如
FirstOrDefaultAsync、ToListAsync)添加过滤条件后抛出'IQueryable' doesn't implement 'IAsyncQueryProvider'异常 - 调整后异步调用返回null,无法正常验证结果
一、解决MockQueryable.NSubstitute的异步查询异常
普通List.AsQueryable()生成的查询对象未实现EF Core要求的IAsyncQueryProvider,导致异步LINQ方法报错。需用MockQueryable将列表转换为支持异步的DbSet:
正确Mock示例
[Fact] public async Task Get_ById_Returns_CorrectRequirement() { // Arrange long requirementId = 7; string ntUser = "anyUser"; // 准备测试数据 var data = new List<TDummyTable> { new TDummyTable { PkKey = 1, Valid = true }, new TDummyTable { PkKey = requirementId, Valid = true }, }; // 构建支持异步的Mock DbSet var mockDbSet = data.AsQueryable().BuildMockDbSet(); // Mock DbContext的表属性 var mockContext = Substitute.For<MyDbContext>(); mockContext.TDummyTable.Returns(mockDbSet); // 初始化被测对象 var repository = new YourRepository(mockContext); var _sut = new YourService(repository); // Act var requirement = await _sut.Get(requirementId, ntUser); // Assert Assert.Equal(requirementId, requirement.PkKey); }
说明:BuildMockDbSet()会自动给查询对象注入EF Core兼容的IAsyncQueryProvider,让异步LINQ方法正常执行。
二、内存数据库异步查询注意事项
内存数据库本身支持异步查询,但需注意以下几点:
- 避免数据污染:每次测试使用随机数据库名,防止跨测试数据干扰
var options = new DbContextOptionsBuilder<MyDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; - 检查仓储过滤逻辑:你提供的仓储方法中存在硬编码过滤
Where(x => x.PkKey == 23),这会直接导致PkKey=7的数据无法被查询到,需根据测试场景调整逻辑。
三、解决编辑补充2中的异步返回null问题
问题根源是GetQueryable()返回的普通IQueryable不支持异步操作,需替换为MockQueryable处理后的异步查询对象:
修正后的测试代码
[Fact] public async Task GetByFilter() { //Arrange string ntUser = "AnyUser"; int projectId = 50; List<TDummyTable> data = new List<TDummyTable>(); data.Add(GenerateSampleData(74)); data.Add(GenerateSampleData(projectId)); data.Add(GenerateSampleData(38)); data.Add(GenerateSampleData(projectId)); data.Add(GenerateSampleData(projectId)); // 将列表转换为支持异步的IQueryable var asyncQueryable = data.AsQueryable().BuildMock(); // Mock仓储的GetQueryable方法 dummyRepository.GetQueryable().Returns(asyncQueryable); // 正确Mock Get方法,处理异步查询 dummyRepository.Get(Arg.Any<IQueryable<TDummyTable>>()) .Returns(async (IQueryable<TDummyTable> query) => { return await query.AsNoTracking().ToListAsync(); }); // Act var result = await _sut.Get(ntUser: ntUser, testingProjectId: projectId); // Assert dummyRepository.Received(1).GetQueryable(); await dummyRepository.Received(1).Get(Arg.Is<IQueryable<TDummyTable>>( query => query.Count() == 3 && query.All(x => x.FkProjectId == projectId) )); Assert.Equal(3, result.Count); Assert.All(result, x => Assert.Equal(projectId, x.FkProjectId)); }
说明:
BuildMock()生成的查询对象支持异步操作,ToListAsync可正常返回结果- Mock
Get方法时直接复用传入的异步查询对象,贴合实际仓储逻辑 - 新增对返回结果的直接断言,更直观验证业务逻辑正确性
关键要点总结
- 普通
List.AsQueryable()不支持EF Core异步LINQ方法,必须用MockQueryable生成兼容的查询对象 - 内存数据库异步查询需保证测试数据隔离,同时检查业务逻辑中的硬编码过滤
- Mock仓储方法时,需确保所有涉及异步的查询对象都实现
IAsyncQueryProvider
内容的提问来源于stack exchange,提问作者user3077796
相关产品推荐
相关产品推荐

