You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Xunit中进行Quartz集成测试?Outbox消息未处理

问题:Xunit测试中Quartz作业不处理Outbox消息

我需要测试一个Quartz作业,该作业负责触发领域事件以修改用户余额,手动运行时一切正常。但在Xunit测试中,该作业完全不处理Outbox消息,ProcessedOnUtc始终为null。

测试代码

[Fact]
public async Task Should_WinAuction_When_BuyoutPrice()
{
    var user = User.Create(Guid.NewGuid(), "example", "example@.com");
    var userTwo = User.Create(Guid.NewGuid(), "example2", "example2@.com");
    var oldBalance = user.Balance;
    _userRepository.Add(user);
    _userRepository.Add(userTwo);
    await UnitOfWork.SaveChangesAsync();
    var auction = Auction.Create(Guid.NewGuid(), "example", "description", 10m, 11m, DateTime.Now, user.Id);
    _auctionRepository.Add(auction);
    await UnitOfWork.SaveChangesAsync();
    var command = new PlaceBidCommand(11m, userTwo.Id, auction.Id);
    var result = await Sender.Send(command);
    Assert.True(result.IsSuccess);
    var bid = await _bidRepository.GetByIdAsync(result.Data.BidId);
    Assert.NotNull(bid);
    Assert.Equal(userTwo.Id, auction.WinnerId);
    await Task.Delay(5000);
    var updatedUser = DbContext.Users.FirstOrDefault(u => u.Id == userTwo.Id);

    Assert.True(updatedUser.Balance != 10000);
}

Quartz注册代码

// Quartz 服务注册
services.AddQuartz(options =>
{
    var jobKey = JobKey.Create(nameof(ProcessOutboxMessages));
    options.SchedulerId = Guid.NewGuid().ToString();
    options.SchedulerName = "OutboxMessages";
    options.AddJob<ProcessOutboxMessages>(jobKey)
    .AddTrigger(trigger => trigger.ForJob(jobKey).WithSimpleSchedule(schedule =>
    {
        schedule.WithIntervalInSeconds(1);
        schedule.RepeatForever();
    }));
});
services.AddQuartzHostedService(options =>
{
    options.WaitForJobsToComplete = true;
});

作业代码

public async Task Execute(IJobExecutionContext context)
{
    var messages = await _dbContext.OutboxMessages
        .Where(x => x.ProcessedOnUtc == null)
        .Take(20)
        .ToListAsync();
    foreach (var message in messages)
    {
        IDomainEvent? domainEvent = JsonConvert.DeserializeObject<IDomainEvent>(message.Content, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
        if (domainEvent == null)
        {
            continue;
        }
        await _publisher.Publish(domainEvent);
        message.ProcessedOnUtc = DateTime.UtcNow;
    }

    await _dbContext.SaveChangesAsync();
}
解决方案

1. 手动启动Quartz调度器

Xunit测试宿主不会自动启动后台服务,需要在测试中手动启动Quartz调度器:

private readonly IScheduler _scheduler;

public YourTestClass(IScheduler scheduler)
{
    _scheduler = scheduler;
}

[Fact]
public async Task Should_WinAuction_When_BuyoutPrice()
{
    await _scheduler.Start();
    // 原测试代码逻辑...
}

2. 确保DbContext实例一致

测试与作业使用的DbContext可能不是同一个实例,导致作业读取不到测试生成的消息:

  • 检查DI配置,确保DbContext注册为Scoped;
  • 在测试中通过同一个服务作用域获取DbContext,避免实例隔离。

3. 解决事务隔离问题

测试中保存消息的事务可能未提交,导致作业无法读取未提交数据:

  • 确认UnitOfWork.SaveChangesAsync()已提交事务;
  • 若使用事务性测试框架,确保作业执行前事务已完成提交。

4. 手动触发作业替代延迟等待

Task.Delay(5000)不可靠,可直接触发作业执行:

// 方式1:直接调用作业逻辑
var job = new ProcessOutboxMessages(_dbContext, _publisher);
await job.Execute(null);

// 方式2:通过调度器触发作业
await _scheduler.TriggerJob(JobKey.Create(nameof(ProcessOutboxMessages)));

5. 校验序列化配置

确保生成Outbox消息和作业反序列化时使用相同的JsonSerializerSettings,必须启用TypeNameHandling.All,否则无法正确识别具体领域事件类型。


内容的提问来源于stack exchange,提问作者Pentium3k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:27:42