C
C#5d ago
Victor H

Configuration of Testcontainer, WebApplicationFactory and ConfigurationSource in ASP.NET Core.

Hi, I'm trying to figure out a nice approach to setup my tests. I am writing an application where I am using the the Options pattern in my Program.cs to setup some connections option:
// Program.cs
builder.Services.AddOptions<PostgreSqlConnectionOptions>().BindConfiguration("PostgreSqlConnectionOptions");
builder.Services.AddOptions<RabbitMqConnectionOptions>().BindConfiguration("RabbitMqConnectionOptions");
// Program.cs
builder.Services.AddOptions<PostgreSqlConnectionOptions>().BindConfiguration("PostgreSqlConnectionOptions");
builder.Services.AddOptions<RabbitMqConnectionOptions>().BindConfiguration("RabbitMqConnectionOptions");
where for instance:
// PostgreSqlConnectionOptions.cs
public class PostgreSqlConnectionOptions
{
public required string Host { get; set; } = "localhost";
public required string Database { get; set; } = "myDb";
public required string Username { get; set; } = "username";
public required string Password { get; set; } = "password";
public required int Port { get; set; } = 5432;
}
// PostgreSqlConnectionOptions.cs
public class PostgreSqlConnectionOptions
{
public required string Host { get; set; } = "localhost";
public required string Database { get; set; } = "myDb";
public required string Username { get; set; } = "username";
public required string Password { get; set; } = "password";
public required int Port { get; set; } = 5432;
}
In my tests where I am using WebApplicationFactory together with Testcontainers I want to improve my setup. Can you offer me any advice on how to improve my setup code (next message).
3 Replies
Victor H
Victor HOP5d ago
This is my setup:
public class ApiFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly RabbitMqTestContainer _rabbitMqTestContainer = new();
private readonly PostgreSqlTestContainer _postgreSqlTestContainer = new();

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(config =>
{
var rabbitMqTestConfiguration = new Dictionary<string, string>
{
["RabbitMqConnectionOptions:HostName"] = _rabbitMqTestContainer.Hostname,
["RabbitMqConnectionOptions:Port"] = _rabbitMqTestContainer.HostPort.ToString(),
["RabbitMqConnectionOptions:UserName"] = RabbitMqTestContainer.UserName,
["RabbitMqConnectionOptions:Password"] = RabbitMqTestContainer.Password
};
config.AddInMemoryCollection(rabbitMqTestConfiguration!);

var postgreSqlTestConfiguration = new Dictionary<string, string>
{
["PostgreSqlConnectionOptions:Host"] = _postgreSqlTestContainer.Hostname,
["PostgreSqlConnectionOptions:Database"] = PostgreSqlTestContainer.Database,
["PostgreSqlConnectionOptions:Username"] = PostgreSqlTestContainer.Username,
["PostgreSqlConnectionOptions:Password"] = PostgreSqlTestContainer.Password,
["PostgreSqlConnectionOptions:Port"] = _postgreSqlTestContainer.HostPort.ToString()
};
config.AddInMemoryCollection(postgreSqlTestConfiguration!);
});
}
}
public class ApiFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly RabbitMqTestContainer _rabbitMqTestContainer = new();
private readonly PostgreSqlTestContainer _postgreSqlTestContainer = new();

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(config =>
{
var rabbitMqTestConfiguration = new Dictionary<string, string>
{
["RabbitMqConnectionOptions:HostName"] = _rabbitMqTestContainer.Hostname,
["RabbitMqConnectionOptions:Port"] = _rabbitMqTestContainer.HostPort.ToString(),
["RabbitMqConnectionOptions:UserName"] = RabbitMqTestContainer.UserName,
["RabbitMqConnectionOptions:Password"] = RabbitMqTestContainer.Password
};
config.AddInMemoryCollection(rabbitMqTestConfiguration!);

var postgreSqlTestConfiguration = new Dictionary<string, string>
{
["PostgreSqlConnectionOptions:Host"] = _postgreSqlTestContainer.Hostname,
["PostgreSqlConnectionOptions:Database"] = PostgreSqlTestContainer.Database,
["PostgreSqlConnectionOptions:Username"] = PostgreSqlTestContainer.Username,
["PostgreSqlConnectionOptions:Password"] = PostgreSqlTestContainer.Password,
["PostgreSqlConnectionOptions:Port"] = _postgreSqlTestContainer.HostPort.ToString()
};
config.AddInMemoryCollection(postgreSqlTestConfiguration!);
});
}
}
And an example of my Testcontainer class:
public class PostgreSqlTestContainer : ITestContainer {

private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
.WithDatabase(Database)
.WithUsername(Username)
.WithPassword(Password)
.WithPortBinding(ContainerPort, true)
.Build();

public const string Database = "myDb";
public const string Username = "username";
public const string Password = "password";
private const ushort ContainerPort = 5432; // PostgreSQL default port
public string ConnectionString => _container.GetConnectionString();
public ushort HostPort => _container.GetMappedPublicPort(ContainerPort);
public string Hostname => _container.Hostname;

public Task InitializeAsync() => _container.StartAsync();
public Task DisposeAsync() => _container.DisposeAsync().AsTask();
}
public class PostgreSqlTestContainer : ITestContainer {

private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
.WithDatabase(Database)
.WithUsername(Username)
.WithPassword(Password)
.WithPortBinding(ContainerPort, true)
.Build();

public const string Database = "myDb";
public const string Username = "username";
public const string Password = "password";
private const ushort ContainerPort = 5432; // PostgreSQL default port
public string ConnectionString => _container.GetConnectionString();
public ushort HostPort => _container.GetMappedPublicPort(ContainerPort);
public string Hostname => _container.Hostname;

public Task InitializeAsync() => _container.StartAsync();
public Task DisposeAsync() => _container.DisposeAsync().AsTask();
}
becquerel
becquerel5d ago
Honestly, that looks pretty good to me - or at least it's similar to what I've done before when writing similar integration tests there is a lot of boilerplate but I think it just comes with the territory unfortunately. were you encountering any particular pain points?
Victor H
Victor HOP5d ago
Not anything particularly annoying but just always thinking about improving my code. For instance, as you said, it becomes a bit boilerplate to add new containers and I don't like having to type strings into a dictionary. But nothing is terrible or a pain point just looking for feedback 😄 Thanks for your response ❤️

Did you find this page helpful?