如何在单元测试中防止EF自动加载导航属性
EF Core单元测试与API运行时导航属性行为不一致的解决方案
这个问题我之前也踩过坑,核心原因是单元测试中共享了同一个DbContext实例,导致变更追踪器自动修复导航属性,和实际API的上下文生命周期不一致造成的。
问题根源分析
你在单元测试里,先通过同一个TestContext实例添加了App和AppRevision并保存,接着用同一个上下文创建处理程序查询App。此时EF Core的变更追踪器已经缓存了之前添加的AppRevision实体,当你查询到App后,它会自动帮你填充AppRevisions导航属性——这是EF Core变更追踪器的"关系修复"特性。
而在实际API运行时,每个请求都会创建全新的DbContext实例,变更追踪器是空的,没有之前缓存的AppRevision,所以没显式Include的导航属性自然是空集合,完全符合你的预期。
可行解决方案
最贴合真实生产环境的方案是使用分离的DbContext实例来初始化测试数据和执行命令处理,模拟API中每个请求新上下文的生命周期:
[Fact] public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid() { Guid testGuid = Guid.NewGuid(); CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc@abc.com", null); // 第一阶段:用独立上下文初始化测试数据 using (var seedContext = new TestContextFactory().CreateTestContext()) { seedContext.Apps.Add(new App() { Id = testGuid }); seedContext.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true }); await seedContext.SaveChangesAsync(); } // 第二阶段:用全新上下文执行命令处理,模拟API请求的上下文隔离 using (TestContext context = new TestContextFactory().CreateTestContext()) { CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(context); CommandResult result = await handler.Handle(command, new CancellationToken()); Assert.True(result); Assert.Single(context.Installations); // 额外验证:此时App的AppRevisions应该是空集合,和API运行时一致 var app = await context.Apps.FindAsync(testGuid); Assert.Empty(app.AppRevisions); } }
为什么这个方案有效?
Sqlite内存数据库(:memory:)会在同一个连接字符串下共享数据库实例,所以第一个上下文保存的数据,第二个上下文完全可以查询到。但每个DbContext的变更追踪器是独立的,第二个上下文的变更追踪器没有之前缓存的AppRevision,自然不会自动填充导航属性,完美模拟了API运行时的行为。
如果你不想拆分上下文,另一个临时方案是在查询App后手动清空AppRevisions,但这会让测试代码不够直观,不如分离上下文的方案更贴近真实场景。
内容的提问来源于stack exchange,提问作者pr.lwd
相关产品推荐
相关产品推荐

