为内存中的 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.
上提出问题
我目前正在尝试使用 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.
上提出问题