WebApplicationFactory集成测试:Redis配置覆盖顺序异常排查
问题描述
我正在为一个通过Redis通信的Web API编写集成测试,尝试用容器化Redis替代原有Redis服务器执行测试。问题在于,WebApplicationFactory会先使用项目的appsettings.Development.json配置和旧的IConnectionMultiplexer实例启动API,而旧配置中的主机已离线,导致连接失败。我需要让API使用连接容器化Redis的新IConnectionMultiplexer,但当前执行顺序错误:先启动旧配置的API再替换服务,连接异常阻碍了测试进行。此外,我还有其他基于配置注入的服务,因此必须先覆盖配置,再启动API代码。
API代码
public static class RedisConnectionConfiguration { public static void AddRedisConnection(this IServiceCollection serviceCollection, IConfiguration config) { var redisConfig = config.GetSection("Redis").Get<RedisConfiguration>(); serviceCollection.AddHostedService<RedisSubscription>(); serviceCollection.AddSingleton(redisConfig); var redis = ConnectionMultiplexer.Connect(redisConfig.Host); // 这里失败,因为还没覆盖Redis:Host配置 serviceCollection.AddSingleton<IConnectionMultiplexer>(redis); } }
集成测试代码
OrderManagerApiFactory
public class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime { private const string Password = "Test1234!"; private readonly TestcontainersContainer _redisContainer; private readonly int _externalPort = Random.Shared.Next(10_000, 60_000); public OrderManagerApiFactory() { _redisContainer = new TestcontainersBuilder<TestcontainersContainer>() .WithImage("redis:alpine") .WithEnvironment("REDIS_PASSWORD", Password) .WithPortBinding(_externalPort, 6379) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379)) .Build(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Development"); builder.ConfigureLogging(logging => { logging.ClearProviders(); }); builder.ConfigureAppConfiguration(config => { config.AddInMemoryCollection(new Dictionary<string, string> { { "Redis:Host", $"localhost:{_externalPort},password={Password},allowAdmin=true" }, { "Redis:Channels:Main", "main:new:order" }, }); }); builder.ConfigureTestServices(services => { services.RemoveAll(typeof(IConnectionMultiplexer)); services.AddSingleton<IConnectionMultiplexer>(_ => ConnectionMultiplexer.Connect($"localhost:{_externalPort},password={Password},allowAdmin=true")); }); } public async Task InitializeAsync() { await _redisContainer.StartAsync(); } public new async Task DisposeAsync() { await _redisContainer.DisposeAsync(); } }
OrderManagerTests
public class OrderManagerTests : IClassFixture<OrderManagerApiFactory>, IAsyncLifetime { private readonly OrderManagerApiFactory _apiFactory; public OrderManagerTests(OrderManagerApiFactory apiFactory) { _apiFactory = apiFactory; } [Fact] public async Task Test() { // Arrange var configuration = _apiFactory.Services.GetRequiredService<IConfiguration>(); var redis = _apiFactory.Services.GetRequiredService<IConnectionMultiplexer>(); var channel = configuration.GetValue<string>("Redis:Channels:Main"); // Act await redis.GetSubscriber().PublishAsync(channel, "ping"); // Assert } public Task InitializeAsync() { return Task.CompletedTask; } public Task DisposeAsync() { return Task.CompletedTask; } }
解决方案
核心问题是API在服务注册阶段就直接创建了Redis连接,此时测试的配置覆盖还未生效,导致使用旧配置连接失败。以下是具体修复步骤:
1. 修改API的Redis注册逻辑,延迟连接创建
将直接创建ConnectionMultiplexer改为工厂模式注入,让连接逻辑延迟到第一次使用实例时执行,确保此时测试配置已经覆盖完成。
修改后的RedisConnectionConfiguration:
public static class RedisConnectionConfiguration { public static void AddRedisConnection(this IServiceCollection serviceCollection, IConfiguration config) { var redisConfig = config.GetSection("Redis").Get<RedisConfiguration>(); serviceCollection.AddHostedService<RedisSubscription>(); serviceCollection.AddSingleton(redisConfig); // 改用工厂模式,延迟创建连接,直到第一次获取IConnectionMultiplexer时执行 serviceCollection.AddSingleton<IConnectionMultiplexer>(sp => { var redisSettings = sp.GetRequiredService<RedisConfiguration>(); return ConnectionMultiplexer.Connect(redisSettings.Host); }); } }
2. 简化测试工厂配置,利用覆盖后的配置自动创建连接
现在配置覆盖会先于连接创建生效,无需手动移除和重新注册IConnectionMultiplexer,测试工厂可简化为:
public class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime { private const string Password = "Test1234!"; private readonly TestcontainersContainer _redisContainer; public OrderManagerApiFactory() { _redisContainer = new TestcontainersBuilder<TestcontainersContainer>() .WithImage("redis:alpine") .WithEnvironment("REDIS_PASSWORD", Password) .WithPortBinding(6379, true) // 自动分配外部端口,避免冲突 .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379)) .Build(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Development"); builder.ConfigureLogging(logging => { logging.ClearProviders(); }); builder.ConfigureAppConfiguration(configBuilder => { // 获取容器自动分配的映射端口 var redisPort = _redisContainer.GetMappedPort(6379); configBuilder.AddInMemoryCollection(new Dictionary<string, string> { { "Redis:Host", $"localhost:{redisPort},password={Password},allowAdmin=true" }, { "Redis:Channels:Main", "main:new:order" }, }); }); } public async Task InitializeAsync() { await _redisContainer.StartAsync(); } public new async Task DisposeAsync() { await _redisContainer.DisposeAsync(); } }
3. 优化说明
- 延迟连接创建:避免在服务注册阶段(配置未覆盖时)执行Redis连接,确保使用测试环境的容器化Redis配置。
- 自动端口分配:Testcontainers自动分配外部端口,避免手动指定端口导致的冲突问题,提升测试稳定性。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

