You need to enable JavaScript to run this app. .NET Core 5.0下使用Moq模拟服务接口结合DbContext单元测试问题排查
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 5.0下使用Moq模拟服务接口结合DbContext单元测试问题排查

问题排查与修复方案

核心问题梳理

  • 测试方向完全错误:你要验证的是自己写的InventoryService业务逻辑,反而直接Mock了IInventoryService接口,全程没有用到你实现的服务代码,测试跑的全是Moq的模拟返回,自然和你写的业务逻辑无关。
  • 依赖Mock对象搞错:InventoryService唯一的依赖是DBContext,你只需要MockDBContext以及对应的DbSet属性,再把Mock后的DBContext注入到真实的InventoryService实例中,才能真正测到业务代码。
  • 缺少必要的模拟配置:DeleteInventory方法中用到了DbSet.Find()方法,该方法不属于IQueryable接口,你目前只配置了IQueryable相关的四个属性,不单独Setup的话调用Find会默认返回null,导致删除逻辑直接返回false。
  • 断言逻辑无效:比如创建库存的测试里断言data不为空,data是你自己初始化的空列表,必然不为空,完全无法验证创建逻辑是否生效。

修正后的测试代码示例

public class InventoryTests
{
    // 复用的Mock DbSet构建方法,避免每个测试重复写代码
    private Mock<DbSet<T>> BuildMockDbSet<T>(IQueryable<T> data) where T : class
    {
        var mockSet = new Mock<DbSet<T>>();
        mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
        return mockSet;
    }

    [Fact]
    public void Get_user_inventories_with_valid_id()
    {
        // ARRANGE
        Guid theId1 = new("00000000-0000-0000-0000-000000000001");
        string u1 = "xxx";
        string u2 = "yyy";

        var inventoryData = new List<Inventory>
        {
            new Inventory { Id = theId1, name = "Mums 1", userId = u1},
            new Inventory { Id = Guid.NewGuid(), name = "Mums 1.2", userId = u1},
            new Inventory { Id = Guid.NewGuid(), name = "Other user's inv", userId = u2}
        }.AsQueryable();

        // Mock DbSet和DbContext
        var mockInventorySet = BuildMockDbSet(inventoryData);
        var mockDbContext = new Mock<DBContext>();
        mockDbContext.Setup(c => c.Inventories).Returns(mockInventorySet.Object);

        // 初始化真实的被测服务,注入Mock的DbContext
        var inventoryService = new InventoryService(mockDbContext.Object);

        // ACT
        var tinvs = inventoryService.GetInventories(u1);

        // ASSERT
        Assert.Equal(2, tinvs.Count);
        Assert.All(tinvs, inv => Assert.Equal(u1, inv.userId));
    }

    [Fact]
    public void Creating_An_Inventory()
    {
        // ARRANGE
        var inventoryData = Enumerable.Empty<Inventory>().AsQueryable();
        var mockInventorySet = BuildMockDbSet(inventoryData);
        var mockDbContext = new Mock<DBContext>();
        mockDbContext.Setup(c => c.Inventories).Returns(mockInventorySet.Object);

        var inventoryService = new InventoryService(mockDbContext.Object);
        var newInventory = new Inventory { Id = Guid.NewGuid(), name = "Mums 2", userId = "yyy" };

        // ACT
        inventoryService.CreateInventory(newInventory);

        // ASSERT:验证Add和SaveChanges被正确调用,确认创建逻辑生效
        mockInventorySet.Verify(s => s.Add(It.Is<Inventory>(i => i.Id == newInventory.Id)), Times.Once);
        mockDbContext.Verify(c => c.SaveChanges(), Times.Once);
    }

    [Fact]
    public void Deleting_An_Inventory()
    {
        // ARRANGE
        Guid targetId = new("00000000-0000-0000-0000-000000000001");
        string u1 = "123";

        var inventoryData = new List<Inventory>
        {
            new Inventory { Id = targetId, name = "testinv 1", userId = u1},
        }.AsQueryable();

        var mockInventorySet = BuildMockDbSet(inventoryData);
        // 单独Setup Find方法,匹配目标Id时返回对应数据
        mockInventorySet.Setup(s => s.Find(targetId)).Returns(inventoryData.First(i => i.Id == targetId));

        // 模拟Items DbSet,避免删除关联Item时报错
        var mockItemSet = BuildMockDbSet(Enumerable.Empty<Item>().AsQueryable());

        var mockDbContext = new Mock<DBContext>();
        mockDbContext.Setup(c => c.Inventories).Returns(mockInventorySet.Object);
        mockDbContext.Setup(c => c.Items).Returns(mockItemSet.Object);

        var inventoryService = new InventoryService(mockDbContext.Object);

        // ACT
        var deleteResult = inventoryService.DeleteInventory(targetId, u1);

        // ASSERT
        Assert.True(deleteResult);
        mockInventorySet.Verify(s => s.Remove(It.Is<Inventory>(i => i.Id == targetId)), Times.Once);
        mockDbContext.Verify(c => c.SaveChanges(), Times.Once);
    }
}

补充说明

测试增删逻辑时,不需要修改模拟的数据源然后判断数量,直接验证DbContext的对应方法是否被正确调用即可,符合单元测试隔离性的要求。如果需要测试关联数据删除逻辑,自行给Items的Mock DbSet配置对应测试数据即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 02:54:04