EF Core 内存数据库测试不稳定:查询之间的数据不一致

问题描述 投票:0回答:1

我正在使用 EF Core 的内存数据库对处理事件队列并更新数据库中现有事件的作业进行单元测试。问题是我的测试有时会通过,但经常失败,数据检索不一致。具体来说,测试有时无法找到在测试设置期间显式添加到数据库的事件实体。

问题总结:

  1. 我使用 PopulateDb 方法中的 DbContext 将数据添加到数据库。
  2. 在执行作业期间,我使用 FirstOrDefaultAsync 查询事件表。
  3. 有时,即使数据已成功添加到 PopulateDb 中,FirstOrDefaultAsync 查询也会返回 null。

这是一个例子:

填充数据库功能

private async Task PopulateDb(ApplicationDbContext dbContext)
{
    var eventQueue = FakerGenerator.EventQueue()
        .RuleFor(p => p.EventUid, "test-event-01")
        .RuleFor(e => e.Start, DateTime.Parse("2022-01-01T00:00:00"))
        .Generate()

    var existingEvent = FakerGenerator.Event()
        .RuleFor(e => e.Uid, "test-event-01")
        .RuleFor(e => e.StartDate, DateTime.Parse("1999-01-01T00:00:00"))
        .Generate();

    dbContext.EventsQueue.AddRange(eventsQueue);
    var addedEventQueues = await dbContext.SaveChangesAsync();

    dbContext.Events.Add(existingEvent);
    var addedEvents = await dbContext.SaveChangesAsync();

    Assert.Equal(1, addedEventQueues);
    Assert.Equal(1, addedEvents);
}

测试功能

[Fact]
public async Task Execute_ShouldSendEmailsForNewEvents()
{
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
        .UseInMemoryDatabase(Guid.NewGuid().ToString())
        .Options;

    var dbContext = new ApplicationDbContext(options);

    await PopulateDb(dbContext);

    var emailServiceMock = new Mock<EmailService>(dbContext);

    var job = new QueuedEventNotifierJob(
        dbContext,
        emailServiceMock.Object
    );

    await job.Execute();

    var updatedEvent = await dbContext.Events.AsNoTracking().FirstAsync(e => e.Uid == "test-event-01");

    // This part fails most of the time but sometimes it is asserter successfully
    Assert.Equal(DateTime.Parse("2022-01-01T00:00:00"), updatedEvent.StartDate);
}

作业中的执行函数

public async Task Execute()
{
    var events = await dbContext.EventsQueue
        .Where(e => e.Status == EventQueueStatus.New)
        .ToListAsync();

    foreach (var unprocessedEvent in events)
    {
        var existingEvent = await dbContext.Events.FirstOrDefaultAsync(e => e.Uid == unprocessedEvent.EventUid);

        // desperately trying to catch something
        if (unprocessedEvent.EventUid == "test-event-01" && existingEvent == null)
            throw new Exception("Event not found");

        if (existingEvent != null) {
           existingEvent.StartDate = unprocessedEvent.Start;
           dbContext.Events.Update(existingEvent);
           await dbContext.SaveChangesAsync();
        }
    }
}

测试间歇性失败,并出现以下错误:

Xunit.Sdk.EqualException
Assert.Equal() Failure: Values differ
Expected: 2022-01-01T00:00:00.0000000
Actual:   1999-01-01T00:00:00.0000000Z

环境:

  • EF 核心版本:8.0.7
  • 测试框架:xUnit 2.5.3
  • 内存数据库:EF Core UseInMemoryDatabase 9.0.0
.net entity-framework xunit bogus
1个回答
0
投票

对于单元测试,模拟持久性,而不是尝试替换底层数据库/w之类的内存数据库。对于要测试的代码,您需要断言的是实体已被修改或请求添加,而不是测试 EF 实际更新/插入行的事实。 (实体框架的功能已经过测试)有一些示例可以模拟 EF

DbContext
/
DbSet
或者我通常建议使用薄抽象 /w
IQueryable
来使模拟操作更容易。我个人在
DbContext
上使用称为 DbContextScope 的工作单元抽象,以及更容易模拟的
IQueryable
存储库抽象。

对于持久性测试(这是集成类型测试而不是单元测试),请考虑使用与在生产中使用的相同数据库引擎的实例,而不是内存数据库之类的东西。不同的数据库提供程序有不同的行为,内存数据库提供程序特别有各种警告来阻止其使用,即使是为了测试也是如此。 “强烈建议不要使用内存提供程序进行测试”。持久性测试会比使用模拟慢,并且需要一些设置和拆卸,以便它们适合集成,并且您需要确保持久性的所有方面都反映生产中会发生的情况。

© www.soinside.com 2019 - 2024. All rights reserved.