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

使用WithWebHostBuilder结合Hangfire时集成测试任务存储为空的问题

问题分析与解决方案

问题确认

你的猜测完全正确:调用WithWebHostBuilder时会创建全新的WebHost实例,而非在原CustomWebApplicationFactory的容器基础上修改。这会导致:

  1. 原工厂注册的Hangfire内存存储/服务器是第一个实例,任务实际由它执行
  2. 通过WithWebHostBuilder创建的新WebHost会重新注册一套Hangfire服务,包括全新的空内存存储,所以你获取到的是这个未处理任务的新实例

替代解决方案

无需重构为每个连接字符串单独创建Fixture,以下是几种轻量可行的方案:

1. 在工厂中预留配置扩展点

修改CustomWebApplicationFactory,添加可外部配置的委托,直接在初始服务注册阶段调整配置,避免重建WebHost:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    public Action<IConfigurationBuilder>? ConfigureConfiguration { get; set; }
    public Action<IServiceCollection>? ConfigureServices { get; set; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration(config =>
        {
            ConfigureConfiguration?.Invoke(config);
        });

        builder.ConfigureServices(services =>
        {
            // 原Hangfire注册逻辑
            services.AddHangfire(configure => configure.UseInMemoryStorage()).AddHangfireServer(configuration =>
            {
                configuration.WorkerCount = 1;
                configuration.Queues = new[] {"critical", "default" };
            });

            // 允许外部修改服务
            ConfigureServices?.Invoke(services);
        });
    }
}

测试时直接设置工厂的配置委托,替代WithWebHostBuilder:

[Theory]
[InlineData("ConnectionString1")]
[InlineData("ConnectionString2")]
public async Task YourTest(string connectionString)
{
    var factory = new CustomWebApplicationFactory<Startup>
    {
        ConfigureConfiguration = config =>
        {
            // 动态替换连接字符串
            config.AddInMemoryCollection(new Dictionary<string, string>
            {
                {"ConnectionStrings:Default", connectionString}
            });
        }
    };

    var client = factory.CreateClient();
    // 执行测试逻辑...

    // 此时获取的JobStorage就是处理任务的实例
    var jobStorage = factory.Services.GetRequiredService<JobStorage>();
    var monitoringApi = jobStorage.GetMonitoringApi();
    var enqueuedJobs = monitoringApi.EnqueuedJobs("default");
    // 验证任务数量
}

2. 复用原工厂的Hangfire实例

如果必须使用WithWebHostBuilder,可以手动将原工厂的Hangfire服务注入新WebHost,避免重复注册:

var originalFactory = new CustomWebApplicationFactory<Startup>();
var originalJobStorage = originalFactory.Services.GetRequiredService<JobStorage>();
var originalHangfireServer = originalFactory.Services.GetRequiredService<IHangfireServer>();

var modifiedFactory = originalFactory.WithWebHostBuilder(builder =>
{
    builder.ConfigureServices(services =>
    {
        // 移除新WebHost自动注册的Hangfire服务,替换为原实例
        var existingStorage = services.SingleOrDefault(d => d.ServiceType == typeof(JobStorage));
        if (existingStorage != null) services.Remove(existingStorage);
        services.AddSingleton(originalJobStorage);

        var existingServer = services.SingleOrDefault(d => d.ServiceType == typeof(IHangfireServer));
        if (existingServer != null) services.Remove(existingServer);
        services.AddSingleton(originalHangfireServer);

        // 其他配置修改(如连接字符串)
    });
});

注意需确保原Hangfire实例不会被提前释放,维持正确的生命周期。

3. 使用静态共享内存存储

修改工厂的Hangfire注册逻辑,用静态实例确保所有WebHost复用同一个内存存储:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    private static readonly InMemoryStorage _sharedStorage = new InMemoryStorage();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            services.AddHangfire(configure => configure.UseStorage(_sharedStorage)).AddHangfireServer(configuration =>
            {
                configuration.WorkerCount = 1;
                configuration.Queues = new[] {"critical", "default" };
            });
        });
    }
}

这种方式需注意测试隔离,每个测试前清空存储:

[Fact]
public async Task YourTest()
{
    // 测试前清空共享存储
    var storage = (InMemoryStorage)JobStorage.Current;
    storage.GetMonitoringApi().DeleteAllJobs();

    // 执行测试逻辑...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:07:50