使用WithWebHostBuilder结合Hangfire时集成测试任务存储为空的问题
问题分析与解决方案
问题确认
你的猜测完全正确:调用WithWebHostBuilder时会创建全新的WebHost实例,而非在原CustomWebApplicationFactory的容器基础上修改。这会导致:
- 原工厂注册的Hangfire内存存储/服务器是第一个实例,任务实际由它执行
- 通过
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
相关产品推荐
相关产品推荐

