【问题标题】:Setting up an Autofixture for In-memory DbContext为内存 DbContext 设置自动固定装置
【发布时间】:2020-02-27 09:36:40
【问题描述】:

我目前正在尝试使用 Autofixture 创建一个预定义的夹具,作为使用 In-Memory provider 的 ApplicationDbContext 的 ICustomization 的实现。

public class ApplicationDbContextFixture : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimenFactory = new SpecimenFactory<ApplicationDbContext>(CreateDbContext);
        fixture.Customize<ApplicationDbContext>(
                composer =>
                    composer.FromFactory(specimenFactory)
                );
    }

    /// <summary>
    /// Private factory method to create a new instance of <see cref="ApplicationDbContext"/>
    /// </summary>
    private ApplicationDbContext CreateDbContext()
    {
        var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                    .UseInMemoryDatabase("SomeDatabaseName")
                    .Options;
        var dbContext = new ApplicationDbContext(dbContextOptions);        
        return dbContext;
    }
}

然后,我将按如下方式将该自定义应用到我的 Fixture:

    [Fact]
    public void TestAddUsersToEmptyDatabase()
    {
        // Arrange
        // Fixture for ApplicationDbContext
        var fixture = FixtureFactory.CreateFixture();
        var applicationDatabaseFixture = new ApplicationDbContextFixture();
        fixture.Customize(applicationDatabaseFixture);

        // Fixture for users
        var randomUser = fixture.Create<AppUser>();
        var normalUser = fixture.Create<AppUser>();
        var adminUser = fixture.Create<AppUser>();

        // Act & Assert
        // Run the test against one instance of the context
        // Use a clean instance of the context for each operation too
        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            Assert.Empty(dbContext.Users);
            dbContext.Users.Add(randomUser);
            dbContext.SaveChanges();
        }

        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            dbContext.Users.AddRange(normalUser, adminUser);
            dbContext.SaveChanges();
        }

        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            Assert.NotEmpty(dbContext.Users);
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == randomUser.Id));
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == normalUser.Id));
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == adminUser.Id));
        }
    }

FixtureFactory.CreateFixture 实现

    /// <summary>
    /// Factory method to declare a single <see cref="IFixture"/> for unit tests applications
    /// </summary>
    internal static class FixtureFactory
    {
        internal static IFixture CreateFixture()
        {
            var fixture = new Fixture().Customize(
                new AutoMoqCustomization { ConfigureMembers = true });

            return fixture;
        }
    }

现在在我的单元测试中,断言Assert.Empty(dbContext.Users); 将抛出System.NotImplementedException : The method or operation is not implemented.,因为从Autofixture 生成的DbSet&lt;AppUser&gt; Users 是一个DynamicProxy。

见图片dbContext.Users as DynamicProxy

奇怪的是,如果我检查从fixture.Create&lt;ApplicationDbContext&gt;() 调用的工厂方法(即CreateDbContext())的断点,则 DbSet 用户属于预期类型。

见图片dbContext.Users as InternalDbSet

可选地,我知道我可以将dbContext.Users 的所有用法替换为dbContext.Set&lt;User&gt;(),这将使单元测试通过,但问题是在实际课程中,我将dbContext.Users 用于@ 987654325@和数据库操作,所以我还是要坚持下去。

因此,我需要帮助才能知道为什么 AutoFixture 使用我的工厂方法为我的 ApplicationDbContext 生成实例,但其中的所有 DbSet&lt;&gt; 属性在 ISpecimenBuilder 解析时都会被模拟。有没有办法解决这个问题?

类似的问题我已经在their Github发帖了,但是最近一直不活跃,所以在这里也问了。

请理解,我 2 天前才开始使用 Autofixture。所以如果我写错了什么或者在任何设计模式中有误解,请写评论,以便我吸取教训。

更新 1: 所以我尝试使用没有任何 AutoMoq 自定义的初始化普通夹具(即fixture = new Fixture()),这一次它抛出了一个AutoFixture.ObjectCreationExceptionWithPath 异常,抱怨它无法解析 ApplicationDbContext 中的 DbSet 属性。在这一点上,我在想是否有人知道如何使用 Relay 或 ISpecimenBuilder 来告诉 Autofixture 使用/调用/实现 ApplicationDbContext 中的所有 DbSet&lt;T&gt; 属性和 dbContext.Set&lt;T&gt; 因为如果我在我的单元测试,但正如我所提到的,所有 IQueryable 都是从 DbSets 返回的,所以我不能简单地在 ApplicationDbContext 中替换它。

更新 2: 我从我的工厂方法 CreateDbContext() 中删除并简化了 ApplicationDbContext 的创建,因为它会导致代码复杂性的混乱。

【问题讨论】:

  • 你应该这样断言:Assert.Empty(dbContext.Users.ToList()); 吗?您需要运行 EF 执行命令,在本例中为 ToList()
  • 嗨@Olegl,在我的原始代码中添加它 ToList() 也不会像上面提到的那样工作,因为 Autofixture 正在模拟 DbSet&lt;&gt; 类型的所有属性作为模拟,因为我使用了 AutoMoqCustomization从我的工厂方法。当我现在编写更新 1 时,我发现使用纯 Fixture 而不为我的 ApplicationDbContextFixture 进行任何自定义异常会引发 AutoFixture.ObjectCreationExceptionWithPath 异常,其中 Autofixture 无法为 DbSet 派生实现。
  • 虽然可以为 ApplicationDbContext 中的每个相关属性设置每种类型的 DbSet 的每个固定装置,但这会很乏味,因为我有很多 DbSet。此外,即使在我原来的情况下, dbContext.Set 也可以正常工作,所以我正在考虑使用 Relay 或其他 ISpecimenBuilder 的某种解决方案,这将有助于解决所有 DbSet 以自动从 dbContext.Set 派生 但我缺乏关于如何使用 Autofixture atm 处理的知识。
  • 你想在这里测试什么?好像您对实体框架本身进行了单元测试
  • 单元测试是 POC,表明调用 fixture.Create&lt;ApplicationDbContext&gt; 没有按我的预期工作。如果您查看Microsoft's documentation,这就是您编写测试替身以模拟 EF 行为的方式。引用Microsoft 的话:“您可能希望使用近似连接到真实数据库的东西来测试组件,而不需要实际数据库 I/O 操作的开销。”

标签: c# entity-framework mocking entity-framework-core autofixture


【解决方案1】:

很难从帖子中理解您想要实现的目标。

我认为您真正需要的是测试恰好使用 EntityFramework 的代码。 如果是这种情况,您可能想看看这个库,我创建了EntityFrameworkCore.AutoFixture。它使用 In-Memory 数据库提供程序以及 SQLite 内存提供程序。

查看自述文件以获取一些代码示例。如果您有任何问题,请给我留言或在 GitHub 上打开问题。

【讨论】:

  • 您好,感谢@Andrew 的回复。我已经尝试过了,并将我的 cmets 留在了 Github 线程中。在这里,我将其标记为答案,因为它满足我正在寻找的基本要求(即使用 Autofixture 和 In-Memory Db 为 DbContext 创建固定装置)。但是,我确实遇到了一些问题,所以我给你留下了一些关于原始 github 帖子的问题。为伟大的工作干杯。
  • @UnBroKeN 谢谢。您在 GitHub 线程中提到的问题现已修复,可在最新的预览版本中下载。完整版本很快就会发布。
猜你喜欢
  • 1970-01-01
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 2015-07-24
  • 2010-10-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多