我有一个EF.Core 2.1 DataContext,但尚未为其启用延迟加载。
我的配置如下:
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
我的测试使用相同的DataContext,但是使用不同的选项,例如:
options.UseInMemoryDatabase(databaseName: "ProjectSpecs")
一切正常,除了我的内存DataContext渴望加载所有内容。
如果我要一个实体,它将加载所有相关对象。
这意味着,如果我想实际加载相关属性而忘记这样做,那么在加载相关实体时我的测试就会通过。但是在实际的应用程序中,由于.include
被遗忘而导致失败。
我可以使内存中的DataContext行为与真实的行为相同吗?
答案 0 :(得分:0)
我遇到了同样的问题,在阅读了许多有关该主题的文章后,我得出的结论是,问题确实是因为被测代码是从ChangeTracker中读取的,其中测试代码已经汇编了ChangeTracker。对象图。有了这些知识,我就使用了DbContext并覆盖了SaveChanges方法,如下所示。
public override int SaveChanges()
{
var affectedRows = base.SaveChanges();
if (Database.ProviderName == "Microsoft.EntityFrameworkCore.InMemory")
{
ChangeTracker.Entries()
.Where(e => e.Entity != null)
.ToList()
.ForEach(e => e.State = EntityState.Detached);
}
return affectedRows;
}
通过分离ChangeTracker中的每个对象,它将迫使被测代码返回数据库,而不是从ChangeTracker中提取现有的对象图。
答案 1 :(得分:0)
一个小而重要的观点。急需加载Include()了。 懒惰加载使EF可以在需要时加载实体。您想禁用延迟加载,以便测试急切加载-Include()的正确使用。
默认情况下,禁用延迟加载 。要在测试代码中启用它,请像对应用程序代码一样将UseLazyLoadingProxies()添加到DbContextOptions中。除非最好不要这样做,否则您可以测试一下是否已经准备好了急切的加载。
这里的问题并不完全是您使用的是延迟加载,而是您使用的是与测试数据相同的DbContext来配置测试数据。因此,数据保留在DbContext中,根本没有从内存数据库中加载。
只需确保为安装程序和测试使用其他DbContext。但是,它必须具有相同的数据库名称。实际上,您可以使用完全相同的options对象。
答案 2 :(得分:0)
我认为 Jasper Kent 的答案是更好的方法,因为非测试代码应该尽可能少地知道测试是否存在(即不要搞乱 SaveChanges,因为自动测试需要它)。
我对这个想法的实现如下:
protected IApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase($"ApplicationDbContext_{Guid.NewGuid()}")
.EnableSensitiveDataLogging(true)
.Options;
var dbContext = new ApplicationDbContext(options);
Populate(dbContext);
dbContext.SaveChanges();
// getting another context instance
var newContext = new ApplicationDbContext(options);
return newContext;
}
private void Populate(IApplicationDbContext dbContext)
{
dbContext.EnsureDeleted();
// actual insert of test data in the in-memory database
}
此示例展示了对通用服务的测试,该服务通过标识符获取实体,但也急切加载其详细信息。流利的断言用于断言。
需要深度/完全克隆以确保测试数据永远不会更改,并且在并行运行的测试之间不会共享任何引用。
[Theory]
[InlineData(1)]
[InlineData(2)]
public async Task GetByIdWithIncludeReturnsEntityWithDetails(int entityId)
{
var dbContext = CreateContext();
var includes = new List<Expression<Func<MockModel, object>>> { e => e.MockModelDetail };
var entity = await Instance(dbContext).GetById(entityId, includes);
var expectedHeader = MockModelTestData.MockModelData.FirstOrDefault(e => e.Id == entityId);
var expectedDetails = MockModelTestData.MockModelDetailData.Where(md => md.MockModelId == entityId).DeepClone().ToList();
entity.Should().BeEquivalentTo(expectedHeader,
opt => opt.Including(e => e.Id).Including(e => e.Name));
entity.MockModelDetail.ForEach(md =>
md.Should().BeEquivalentTo(expectedDetails.First(ed => ed.Id == md.Id),
opt => opt.Including(e => e.DetailName).Including(e => e.DateCreation))
);
}
每个测试都会创建自己的数据库上下文,以便它们并行运行(XUnit 的默认设置)。