NUnit测试中Moq模拟DbContext时出现实体无键定义验证错误
解决NUnit测试中EF Identity代理实体无键的验证错误
我之前也碰到过一模一样的问题!核心原因是你用Moq模拟的ApplicationDbContext没有执行EF Identity默认的模型配置,实际运行时的上下文会在OnModelCreating里自动配置ApplicationUserLogin、ApplicationUserRole这些实体的键,所以控制器调用正常,但测试里访问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




