You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

NUnit测试中Moq模拟DbContext时出现实体无键定义验证错误

解决NUnit测试中EF Identity代理实体无键的验证错误

我之前也碰到过一模一样的问题!核心原因是你用Moq模拟的ApplicationDbContext没有执行EF Identity默认的模型配置,实际运行时的上下文会在OnModelCreating里自动配置ApplicationUserLoginApplicationUserRole这些实体的键,所以控制器调用正常,但测试里访问ChangeTracker.Entries()时,EF会扫描所有实体类型,包括那些没配置键的代理类,直接抛出验证错误。

下面给你两种可行的解决方案:

方案1:改用EF内存数据库测试(推荐)

模拟DbContext很容易碰到这类EF内部机制的问题,改用内存数据库能更贴近真实运行环境,还能避免手动配置各种EF细节:

[Test]
public void CreateCustomerNumber_saves_a_CustomerNumber_via_context()
{
    // 创建内存数据库的上下文选项
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
        .UseInMemoryDatabase(databaseName: "Test_CustomerNumber")
        .Options;

    // 实例化真实的上下文(连接内存数据库)
    using (var context = new ApplicationDbContext(options))
    {
        var service = new CustomerNumberService(context);
        service.Add(new CustomerNumberViewModel(){CustomerNumber = "AAA"});
        
        // 验证数据是否被正确保存
        Assert.AreEqual(1, context.CustomerNumbers.Count());
        Assert.AreEqual("AAA", context.CustomerNumbers.First().CustomerNumber);
    }
}

这种方法不需要模拟DbSet和Context,直接用真实的上下文连接内存数据库,所有EF的模型配置(包括Identity的)都会正常执行,SaveChanges里的ChangeTracker操作也能顺畅工作。

方案2:修改模拟上下文,过滤ChangeTracker中的实体

如果你坚持要用Moq,那可以在模拟ChangeTracker.Entries()的时候,只返回你测试中关心的CustomerNumber实体,跳过那些Identity相关的代理类:

[Test]
public void CreateCustomerNumber_saves_a_CustomerNumber_via_context()
{
    var mockSet = new Mock<DbSet<CustomerNumber>>();
    var mockContext = new Mock<ApplicationDbContext>();
    
    // 模拟ChangeTracker.Entries(),只返回CustomerNumber类型的实体
    var mockEntries = new List<DbEntityEntry>();
    mockContext.Setup(c => c.ChangeTracker.Entries())
               .Returns(mockEntries.AsQueryable());

    mockContext.Setup(c => c.CustomerNumbers).Returns(mockSet.Object);
    
    var service = new CustomerNumberService(mockContext.Object);
    service.Add(new CustomerNumberViewModel(){CustomerNumber = "AAA"});
    
    mockSet.Verify(m => m.Add(It.IsAny<CustomerNumber>()), Times.Once());
    mockContext.Verify(m => m.SaveChanges(), Times.Once());
}

另外你之前尝试的TestModelCreation方法没用,因为Moq的上下文实例不会自动调用OnModelCreating,除非手动触发,但手动配置所有Identity实体的键太繁琐,远不如上面两种方法高效。

最后提醒下:测试EF相关逻辑时,内存数据库是更靠谱的选择,模拟DbContext只适合简单场景,复杂点的很容易踩坑~

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

火山引擎 最新活动