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

NUnit测试中Hangfire定时任务调度异常:首次通过后recurringJobDtos始终为空

解决NUnit测试中Hangfire二次执行recurringJobDtos.Any()返回false的问题

问题根源

核心问题出在**JobStorage.Current是静态单例**:

  • 首次测试初始化后,这个静态属性会指向第一次创建的InMemory存储实例;
  • 当测试结束、Host被释放后,该存储实例已失效,但JobStorage.Current并未被重置;
  • 第二次测试启动新Host并配置新的InMemory存储时,静态的JobStorage.Current依然指向旧的失效实例,导致后续调用GetRecurringJobs()无法获取到任何有效数据。

另外原测试代码的循环没有延迟,会导致CPU占用过高,也可能加剧状态不一致的问题。


解决方案

1. 显式重置静态JobStorage实例

在每个测试结束后清理静态资源,确保下一次测试从零开始:

[TearDown]
public void Cleanup()
{
    // 重置静态JobStorage,避免跨测试污染
    JobStorage.Current = null;
}

同时在配置Hangfire时,显式将新创建的存储实例赋值给JobStorage.Current,确保指向正确:

services.AddHangfire(configuration => 
{
    var inMemoryStorage = new InMemoryStorage();
    configuration
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseDefaultTypeSerializer()
        .UseStorage(inMemoryStorage);
    // 绑定当前测试的存储实例到静态属性
    JobStorage.Current = inMemoryStorage;
})
.AddHangfireServer();

2. 从依赖注入容器获取JobStorage(推荐)

避免直接使用静态JobStorage.Current,而是从当前Host的服务容器中获取实例,确保每次拿到的都是当前测试的有效存储:

// 从服务容器获取当前测试对应的JobStorage
var jobStorage = webHost.Services.GetRequiredService<JobStorage>();
var b = false;
do {
    var recurringJobDtos = jobStorage.GetConnection().GetRecurringJobs();
    if (recurringJobDtos.Any()) {
        b = recurringJobDtos.First().LastJobState.StartsWith("Suc");
    }
    // 添加延迟,降低CPU占用并给Job执行留足时间
    await Task.Delay(100);
} while (!b);

完整修改后的测试代码

using Hangfire;
using Hangfire.Server;
using Hangfire.Storage;
using Hangfire.InMemory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;

namespace ClassLibrary1 {
    public class MyClass {

        [TearDown]
        public void Cleanup()
        {
            JobStorage.Current = null;
        }

        [TestCase(1)]
        [TestCase(2)]
        [Test]
        [Order(0)]
        public async Task Test(int not_used) {
            
            var defaultBuilder = Host.CreateDefaultBuilder();
            var webHost = defaultBuilder
                .ConfigureServices(services => {
                    services.AddHangfire(configuration => {
                            var inMemoryStorage = new InMemoryStorage();
                            configuration
                                .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
                                .UseDefaultTypeSerializer()
                                .UseStorage(inMemoryStorage);
                            JobStorage.Current = inMemoryStorage;
                        })
                        .AddHangfireServer();
                })
                .Build();
            await webHost.StartAsync();       

            var jobId = TestContext.CurrentContext.Test.Name;
            RecurringJob.AddOrUpdate(jobId,
                () => new TestJobDI().TestJobId(null), Cron.Never);

            RecurringJob.Trigger(jobId);

            var jobStorage = webHost.Services.GetRequiredService<JobStorage>();
            var b = false;
            do {
                var recurringJobDtos = jobStorage.GetConnection().GetRecurringJobs();
                if (recurringJobDtos.Any()) {
                    b = recurringJobDtos.First().LastJobState.StartsWith("Suc");
                }
                await Task.Delay(100);
            } while (!b);

            await webHost.StopAsync();
            webHost.Dispose();
        }
    }

    public class TestJobDI {
        public void TestJobId(PerformContext context) { }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:42:54