为内存中的 DbContext 设置一个 Autofixture

Setting up an Autofixture for In-memory DbContext

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

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<AppUser> Users 是一个 DynamicProxy。

见图dbContext.Users as DynamicProxy

奇怪的是,如果我检查从 fixture.Create<ApplicationDbContext>() 调用的工厂方法(即 CreateDbContext())的断点,DbSet Users 是预期的类型。

见图dbContext.Users as InternalDbSet

可选,我知道我可以将 dbContext.Users 的所有用法替换为 dbContext.Set<User>(),这将使单元测试通过,但问题是在实际中 class ,我正在使用 dbContext.Users 进行 IQueryables 和数据库操作,所以如果可能的话我仍然需要坚持使用它。

因此,我需要帮助来了解为什么 AutoFixture 使用我的工厂方法为我的 ApplicationDbContext 生成实例,但其中的所有 DbSet<> 属性在 ISpecimenBuilder 解析时都被模拟。有办法解决这个问题吗?

我post在their Github有过类似的问题,但是最近一直不活跃,所以我也在这里问了。

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

更新 1: 所以我尝试使用没有任何 AutoMoq 自定义的初始化普通夹具(即 fixture = new Fixture()),这次它抛出一个 AutoFixture.ObjectCreationExceptionWithPath 异常,抱怨它无法解析 DbSet 属性应用程序数据库上下文。在这一点上,我在想是否有人知道如何使用 Relay 或 ISpecimenBuilder 告诉 Autofixture use/call/implement ApplicationDbContext 中的所有 DbSet<T> 属性为 dbContext.Set<T> 因为如果我替换所有在我的单元测试中使用 DbSets,但正如我提到的,所有 IQueryable 都是来自 DbSets 的 return,所以我不能简单地在 ApplicationDbContext 中替换它。

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

很难理解您要通过 post 实现的目标。

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

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

上提出问题