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

使用NSubstitute测试EF仓储异步查询遇IAsyncQueryProvider异常求助

问题解决:Mock DbContext异步查询测试异常及返回null问题

核心问题梳理

你遇到的两个核心问题:

  1. 使用内存数据库或Mock DbContext时,异步查询(如FirstOrDefaultAsync、ToListAsync)添加过滤条件后抛出'IQueryable' doesn't implement 'IAsyncQueryProvider'异常
  2. 调整后异步调用返回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方法正常执行。


二、内存数据库异步查询注意事项

内存数据库本身支持异步查询,但需注意以下几点:

  1. 避免数据污染:每次测试使用随机数据库名,防止跨测试数据干扰
    var options = new DbContextOptionsBuilder<MyDbContext>()
        .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
        .Options;
    
  2. 检查仓储过滤逻辑:你提供的仓储方法中存在硬编码过滤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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:35:07