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

使用MockQueryable.Moq测试AddRangeAsync时结果始终返回0的问题

解决MockQueryable.Moq测试AddRangeAsync返回0的问题

问题根源

  1. Mock DbSet未绑定动态数据源:初始通过initProducts.AsQueryable().BuildMockDbSet()创建的Mock DbSet,其底层Queryable绑定的是创建时的空列表快照,后续initProducts添加数据后,Mock DbSet的查询接口(如Count())仍会返回初始空列表的结果。
  2. 未MockSaveChangesAsync:服务中调用了_myDb.SaveChangesAsync(),但测试未对该方法做模拟,EF上下文的状态变更逻辑未被正确触发。

解决步骤

步骤1:让Mock DbSet动态返回最新数据源

手动配置Mock DbSet的IQueryable接口,使其每次被查询时都返回当前initProducts的最新状态,替代MockQueryable的静态快照创建方式:

var initProducts = new List<Product>();
var mockProducts = new Mock<DbSet<Product>>();

// 动态绑定IQueryable到最新的initProducts
mockProducts.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(() => initProducts.AsQueryable().Provider);
mockProducts.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(() => initProducts.AsQueryable().Expression);
mockProducts.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(() => initProducts.AsQueryable().ElementType);
mockProducts.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(() => initProducts.AsQueryable().GetEnumerator());

步骤2:完善方法Mock逻辑

  • 保留AddRangeAsync的回调逻辑,确保新实体被加入initProducts
  • 模拟SaveChangesAsync的成功返回,匹配服务中的调用逻辑:
// Mock AddRangeAsync,添加回调并返回完成任务
mockProducts
    .Setup(x => x.AddRangeAsync(It.IsAny<IEnumerable<Product>>(), It.IsAny<CancellationToken>()))
    .Callback((IEnumerable<Product> products, CancellationToken _) => initProducts.AddRange(products))
    .Returns(Task.CompletedTask);

var mockDb = new Mock<MyDb>();
mockDb.Setup(x => x.Products).Returns(mockProducts.Object);
// Mock SaveChangesAsync,返回受影响行数2
mockDb.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(2);

完整修改后的测试代码

[Fact]
public async Task AddRangeTest()
{
    var initProducts = new List<Product>();
    var mockProducts = new Mock<DbSet<Product>>();

    // 配置DbSet动态返回最新数据
    mockProducts.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(() => initProducts.AsQueryable().Provider);
    mockProducts.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(() => initProducts.AsQueryable().Expression);
    mockProducts.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(() => initProducts.AsQueryable().ElementType);
    mockProducts.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(() => initProducts.AsQueryable().GetEnumerator());

    // Mock AddRangeAsync
    mockProducts
        .Setup(x => x.AddRangeAsync(It.IsAny<IEnumerable<Product>>(), It.IsAny<CancellationToken>()))
        .Callback((IEnumerable<Product> products, CancellationToken _) => initProducts.AddRange(products))
        .Returns(Task.CompletedTask);

    var mockDb = new Mock<MyDb>();
    mockDb.Setup(x => x.Products).Returns(mockProducts.Object);
    // Mock SaveChangesAsync
    mockDb.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(2);

    var productService = new ProductService(mockDb.Object);
    await productService.AddRange();

    Assert.Equal(2, mockDb.Object.Products.Count());
}

补充说明

如果希望继续使用MockQueryable.Moq,需要在initProducts更新后重新创建Mock DbSet并重新绑定到MyDb,但这种方式不如手动MockIQueryable灵活,因此更推荐上述手动配置的方案。

内容的提问来源于stack exchange,提问作者Vincent

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:10:51