C
C#2y ago
Hulkstance

Testcontainers must override configuration before it runs the Web API, but it does the opposite.

I'm trying to create an integration test for a Web API whose communication is based on Redis. The issue is that TestcontainersBuilder is first running the project with what's in appsettings.Development.json instead of the overriden configuration by builder.ConfigureAppConfiguration. In other words, the sequence is messed up. Instead of directly running the Redis container, it is first trying to connect to the old IConnectionMultiplexer with the old configuration and then it is supposed to replace that IConnectionMultiplexer with the new one but it makes no sense because it's "the old one" is locally installed and currently not running. The sequence should be 1) override configuration 2) run the web API. I have services e.g. AddFtxServices, AddBinanceServices that I dependency inject based on the configuration which I cannot simply "replace" later on because apparently that's how builder.ConfigureAppConfiguration works, unless there is a workaround that I'm aware of.
14 Replies
Wz
Wz2y ago
you can use ConfigureHostConfiguration
Hulkstance
Hulkstance2y ago
@Wz, but that's part of IHostBuilder https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder.configurehostconfiguration?view=dotnet-plat-ext-6.0 protected override void ConfigureWebHost(IWebHostBuilder builder) that's what i can override
Wz
Wz2y ago
override CreateHost
Wz
Wz2y ago
GitHub
.net6 minimal API test config overrides only apply after app has be...
Describe the bug Using .net 6 minimal APIs and WebApplicationFactory for testing, the test config override is only applied after .Build. This means any interaction with config while configuring ser...
Hulkstance
Hulkstance2y ago
@Wz thanks, it got applied here
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);
serviceCollection.AddSingleton<IConnectionMultiplexer>(redis);
}
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);
serviceCollection.AddSingleton<IConnectionMultiplexer>(redis);
}
unfortunately, it didn't affect
public static void AddFtxServices(this IServiceCollection serviceCollection, IConfiguration config)
{
serviceCollection.Configure<FtxConfiguration>(config.GetSection("Exchanges:Ftx"));

serviceCollection.AddHostedService<FtxSubscriber>();

serviceCollection.AddSingleton<FtxRestService>();

serviceCollection.AddTransient<FtxGateway>();
}
public static void AddFtxServices(this IServiceCollection serviceCollection, IConfiguration config)
{
serviceCollection.Configure<FtxConfiguration>(config.GetSection("Exchanges:Ftx"));

serviceCollection.AddHostedService<FtxSubscriber>();

serviceCollection.AddSingleton<FtxRestService>();

serviceCollection.AddTransient<FtxGateway>();
}
Wz
Wz2y ago
what is suppose to affect AddFtxServices?
Hulkstance
Hulkstance2y ago
@Wz there are exchange configurations in the appsettings such as this one
Hulkstance
Hulkstance2y ago
the issue is that appsettings.Development.json defines Kraken configuration and the test one actually uses a different one (ftx) it resolves the kraken one instead of ftx
Wz
Wz2y ago
You have to add that json specifically Inside ConfigureHostConfiguration
Hulkstance
Hulkstance2y ago
I actually do
Wz
Wz2y ago
Did you tried absolute path?
Wz
Wz2y ago
GitHub
Unable to load/reference appsettings.json when integration testing ...
Hi there 👋 I&#39;m trying to do a basic integration test for a very simple ASP.NET Core WebApi project. I&#39;m leveraging the WebApplicationFactory class and when I do this, my appsettings...
Hulkstance
Hulkstance2y ago
@Wz
public sealed class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime
{
private const string Password = "Test1234!";
private const int ExternalPort = 7777; // Random.Shared.Next(10_000, 60_000);

private readonly TestcontainersContainer _redisContainer;

public OrderManagerApiFactory()
{
_redisContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("redis:alpine")
.WithEnvironment("REDIS_PASSWORD", Password)
.WithPortBinding(ExternalPort, 6379)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
.Build();
}

public async Task InitializeAsync()
{
await _redisContainer.StartAsync();
}

public new async Task DisposeAsync()
{
await _redisContainer.DisposeAsync();
}

protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration(config =>
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Redis:Host", $"localhost:{ExternalPort},password={Password},allowAdmin=true"),
new KeyValuePair<string, string>("Redis:Channels:NewTrade", "oms:order:new:ftx"),
new KeyValuePair<string, string>("Redis:Channels:CancelTrade", "oms:order:cancel:ftx"),
new KeyValuePair<string, string>("Redis:Channels:Updates", "oms:order:updates:ftx"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:Name", "DEV"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:ApiKey", "x"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:ApiSecret", "x"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:SubAccountName", "DEV")
}));

return base.CreateHost(builder);
}
}
public sealed class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime
{
private const string Password = "Test1234!";
private const int ExternalPort = 7777; // Random.Shared.Next(10_000, 60_000);

private readonly TestcontainersContainer _redisContainer;

public OrderManagerApiFactory()
{
_redisContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("redis:alpine")
.WithEnvironment("REDIS_PASSWORD", Password)
.WithPortBinding(ExternalPort, 6379)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
.Build();
}

public async Task InitializeAsync()
{
await _redisContainer.StartAsync();
}

public new async Task DisposeAsync()
{
await _redisContainer.DisposeAsync();
}

protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration(config =>
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Redis:Host", $"localhost:{ExternalPort},password={Password},allowAdmin=true"),
new KeyValuePair<string, string>("Redis:Channels:NewTrade", "oms:order:new:ftx"),
new KeyValuePair<string, string>("Redis:Channels:CancelTrade", "oms:order:cancel:ftx"),
new KeyValuePair<string, string>("Redis:Channels:Updates", "oms:order:updates:ftx"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:Name", "DEV"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:ApiKey", "x"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:ApiSecret", "x"),
new KeyValuePair<string, string>("Exchanges:Ftx:Accounts:0:SubAccountName", "DEV")
}));

return base.CreateHost(builder);
}
}
@Wz it works great thank you very much! 🙂