【发布时间】:2019-12-17 00:03:47
【问题描述】:
我目前正在开发一个应用商店风格的 API,它具有以下实体(还有许多其他实体,但与问题无关):
- App(与 AppRevision 的一对多关系 - 包含 IEnumerable 属性)
- 应用版本
- 安装
我遇到了一个奇怪的问题,即 EF 在单元测试中的行为与实际运行 API 时的行为不同,因为在单元测试时会自动包含导航属性。
从我的命令处理程序中获取以下代码 sn-p:
App app = await this.context.Apps
.Include(a => a.Installations)
.FirstOrDefaultAsync(a => a.Id == command.AppId);
if (app != null) {
// Code omitted for brevity
}
运行 API 时,如果我在运行此代码后检查 app,则 App 实体上的 AppRevisions 集合为空,正如您所期望的那样,因为我没有明确告诉 EF .Include(a => a.AppRevisions) - 然后是 API稍后尝试处理需要此数据的代码时会引发异常。
现在查看以下针对同一处理程序的单元测试:
[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
Guid testGuid = Guid.NewGuid();
CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc@abc.com", null);
using (TestContext context = new TestContextFactory().CreateTestContext())
{
context.Apps.Add(new App() { Id = testGuid });
context.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true });
await context.SaveChangesAsync();
CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(context);
CommandResult result = await handler.Handle(command, new CancellationToken());
Assert.True(result);
Assert.Single(context.Installations);
}
}
如果我单步执行此测试,当我到达处理程序并检查 app 变量时,AppRevisions 集合已自动填充。结果,测试通过,因为需要填充 AppRevisions 集合的代码可以执行。
期望这个测试实际上应该失败,因为我没有告诉 EF 在查询中包含这些实体。
我正在使用内存数据库中的 Sqlite 为我的单元测试创建数据库上下文并运行 .NET Core 2.2
我最初认为这与更改跟踪器有关。虽然禁用它确实解决了上面报告的直接问题,但它会产生大量其他问题,因此不是一个可行的解决方案(而且可能无论如何都不是正确的解决方案)
任何建议都非常感谢
【问题讨论】:
-
您正在重用相同的上下文,因此 AppRevisions 可能存在,因为它们已被缓存。尝试在 SaveChangesAsync 之后创建另一个上下文
-
见stackoverflow.com/questions/42327515/…。不幸的是,他们删除了我所指的部分文档。但原则仍然存在 - 当查询启用了跟踪的实体时,您基本上可以控制加载到导航属性中的内容。如果你真的想要完全控制,那么恐怕你应该切换到 DTO/ViewModels 和投影。
-
与单元测试一样,请确保您尽可能接近真实的应用程序代码。 API 方法没有收到“污染”的上下文。因此,如前所述,为模拟 API 调用的部分使用新的上下文。您可能需要检查有关此模式的其他测试。
-
@Alberto 是的,很可能是这种情况,但是当我使用内存数据库时,我无法创建另一个上下文(或者我可以吗?)
-
@pr.lwd 你创建一个,所以你可以创建两个、三个、四个......
标签: c# sqlite unit-testing entity-framework-core ef-core-2.2