【发布时间】: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<AppUser> Users 是一个DynamicProxy。
见图片dbContext.Users as DynamicProxy
奇怪的是,如果我检查从fixture.Create<ApplicationDbContext>() 调用的工厂方法(即CreateDbContext())的断点,则 DbSet 用户属于预期类型。
见图片dbContext.Users as InternalDbSet
可选地,我知道我可以将dbContext.Users 的所有用法替换为dbContext.Set<User>(),这将使单元测试通过,但问题是在实际课程中,我将dbContext.Users 用于@ 987654325@和数据库操作,所以我还是要坚持下去。
因此,我需要帮助才能知道为什么 AutoFixture 使用我的工厂方法为我的 ApplicationDbContext 生成实例,但其中的所有 DbSet<> 属性在 ISpecimenBuilder 解析时都会被模拟。有没有办法解决这个问题?
类似的问题我已经在their Github发帖了,但是最近一直不活跃,所以在这里也问了。
请理解,我 2 天前才开始使用 Autofixture。所以如果我写错了什么或者在任何设计模式中有误解,请写评论,以便我吸取教训。
更新 1:
所以我尝试使用没有任何 AutoMoq 自定义的初始化普通夹具(即fixture = new Fixture()),这一次它抛出了一个AutoFixture.ObjectCreationExceptionWithPath 异常,抱怨它无法解析 ApplicationDbContext 中的 DbSet 属性。在这一点上,我在想是否有人知道如何使用 Relay 或 ISpecimenBuilder 来告诉 Autofixture 使用/调用/实现 ApplicationDbContext 中的所有 DbSet<T> 属性和 dbContext.Set<T> 因为如果我在我的单元测试,但正如我所提到的,所有 IQueryable 都是从 DbSets 返回的,所以我不能简单地在 ApplicationDbContext 中替换它。
更新 2: 我从我的工厂方法 CreateDbContext() 中删除并简化了 ApplicationDbContext 的创建,因为它会导致代码复杂性的混乱。
【问题讨论】:
-
你应该这样断言:
Assert.Empty(dbContext.Users.ToList());吗?您需要运行 EF 执行命令,在本例中为ToList()。 -
嗨@Olegl,在我的原始代码中添加它 ToList() 也不会像上面提到的那样工作,因为 Autofixture 正在模拟
DbSet<>类型的所有属性作为模拟,因为我使用了AutoMoqCustomization从我的工厂方法。当我现在编写更新 1 时,我发现使用纯 Fixture 而不为我的ApplicationDbContextFixture进行任何自定义异常会引发AutoFixture.ObjectCreationExceptionWithPath异常,其中 Autofixture 无法为 DbSet派生实现。 -
虽然可以为 ApplicationDbContext 中的每个相关属性设置每种类型的 DbSet
的每个固定装置,但这会很乏味,因为我有很多 DbSet。此外,即使在我原来的情况下, dbContext.Set 也可以正常工作,所以我正在考虑使用 Relay 或其他 ISpecimenBuilder 的某种解决方案,这将有助于解决所有 DbSet 以自动从 dbContext.Set 派生 但我缺乏关于如何使用 Autofixture atm 处理的知识。 -
你想在这里测试什么?好像您对实体框架本身进行了单元测试
-
单元测试是 POC,表明调用
fixture.Create<ApplicationDbContext>没有按我的预期工作。如果您查看Microsoft's documentation,这就是您编写测试替身以模拟 EF 行为的方式。引用Microsoft 的话:“您可能希望使用近似连接到真实数据库的东西来测试组件,而不需要实际数据库 I/O 操作的开销。”
标签: c# entity-framework mocking entity-framework-core autofixture