[我目前正在尝试使用Autofixture创建一个预定义的夹具,作为使用ICustomization的ApplicationDbContext的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;
}
}
然后,我将按照以下方式将该定制应用于我的治具:
[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
奇怪的是,如果我检查从CreateDbContext()
调用的工厂方法(即fixture.Create<ApplicationDbContext>()
)的断点,则DbSet用户是预期的类型。
查看图像dbContext.Users as InternalDbSet
[可选,我知道我可以将dbContext.Users
的所有用法替换为dbContext.Set<User>()
,这将使单元测试通过,但问题是在实际的类中,我将dbContext.Users
用于IQueryables ]和数据库操作,因此如果可能,我仍然需要坚持使用它。
因此,我需要帮助来了解为什么AutoFixture为什么使用工厂方法为ApplicationDbContext生成实例,但是其中的所有DbSet<>
属性在由ISpecimenBuilder解析时都会被模拟。有没有办法解决这个问题?
我已经在their Github中发布了类似的问题,但该问题最近并未激活,所以我也在这里问过。
[请注意,我是2天前才开始使用Autofixture的。因此,如果在某些“设计模式”中我写错了什么或存在误解,请发表评论,以备不时之需。
更新1:因此,我尝试使用没有任何AutoMoq定制(即fixture = new Fixture()
)的初始化普通灯具,这一次它抛出AutoFixture.ObjectCreationExceptionWithPath
异常,抱怨它无法解析ApplicationDbContext中的DbSet属性。在这一点上,我在想是否有人知道如何使用Relay或ISpecimenBuilder告诉Autofixture使用DbSet<T>
来使用/调用/实现ApplicationDbContext中的所有dbContext.Set<T>
属性,因为如果我替换了DbSet中的所有用法,那将起作用我的单元测试,但是正如我提到的,所有IQueryable都是从DbSets返回的,所以我不能简单地在ApplicationDbContext中替换它。
更新2:我从工厂方法CreateDbContext()中删除并简化了ApplicationDbContext的创建,因为这将导致代码复杂性的混淆。
很难理解您要通过帖子实现的目标。
我认为您真正需要的是测试恰好使用EntityFramework的代码。如果是这种情况,您可能想看看这个库,我已经创建了EntityFrameworkCore.AutoFixture。它使用内存数据库提供程序以及SQLite内存提供程序。
查看一些代码示例的自述文件。如果您有任何疑问,请给我发消息或在GitHub上发布问题。