【问题标题】:How to prevent automatic inclusion of navigation properties in unit tests如何防止在单元测试中自动包含导航属性
【发布时间】: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


【解决方案1】:

对于将来遇到此帖子的任何人,解决方案是根据原始问题上的 cmets,使用单独的上下文来播种测试数据并在稍后的测试中获取数据:

[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
    Guid testGuid = Guid.NewGuid();

    CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc@abc.com", null);

    using (TestContextFactory contextFactory = new TestContextFactory())
    {
        using (TestContext seedContext = contextFactory.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();
        }

        using (TestContext getContext = contextFactory.CreateTestContext())
        {
            CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(getContext);

            CommandResult result = await handler.Handle(command, new CancellationToken());

            Assert.True(result);
            Assert.Single(getContext.Installations);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-23
    • 2016-01-23
    相关资源
    最近更新 更多