There are another two ways to leverage Testcontainers for .NET in-process into your ASP.NET application and even a third way out-of-process without any dependencies to the application.
1. Using .NET's configuration providers
A very simple in-process setup passes the database connection string using the environment variable configuration provider to the application. You do not need to mess around with the WebApplicationFactory. All you need to do is set the configuration before creating the WebApplicationFactory instance in your tests.
The example below passes the HTTPS configuration incl. the database connection string of a Microsoft SQL Server instance spun up by Testcontainers to the application.
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "https://+");
Environment.SetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Path", "certificate.crt");
Environment.SetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Password", "password");
Environment.SetEnvironmentVariable("ConnectionStrings__DefaultConnection", _mssqlContainer.ConnectionString);
_webApplicationFactory = new WebApplicationFactory<Program>();
_serviceScope = _webApplicationFactory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
_httpClient = _webApplicationFactory.CreateClient();
This example follows the mentioned approach above.
2. Using .NET's hosted service
A more advanced approach spins up the dependent database and seeds it during the application start. It not just helps writing better integration tests, it integrates well into daily development and significantly improves the development experience and productivity.
Spin up the dependent container by implementing IHostedService:
public sealed class DatabaseContainer : IHostedService
{
private readonly TestcontainerDatabase _container = new TestcontainersBuilder<MsSqlTestcontainer>()
.WithDatabase(new DatabaseContainerConfiguration())
.Build();
public Task StartAsync(CancellationToken cancellationToken)
{
return _container.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _container.StopAsync(cancellationToken);
}
public string GetConnectionString()
{
return _container.ConnectionString;
}
}
Add the hosted service to your application builder configuration:
builder.Services.AddSingleton<DatabaseContainer>();
builder.Services.AddHostedService(services => services.GetRequiredService<DatabaseContainer>());
Resolve the hosted service and pass the connection string to your database context:
builder.Services.AddDbContext<MyDbContext>((services, options) =>
{
var databaseContainer = services.GetRequiredService<DatabaseContainer>();
options.UseSqlServer(databaseContainer.GetConnectionString());
});
This example uses .NET's hosted service to leverage Testcontainers into the application start. By overriding the database context's OnModelCreating(ModelBuilder), this approach even takes care of creating the database schema and seeding data via Entity Framework while developing and testing.
3. Running inside a container
In some use cases, it might be necessary or a good approach to run the application out-of-process and inside a container. This increases the level of abstractions and removes the direct dependencies to the application. The services are only available through their public API (e.g. HTTP(S) endpoint).
The configuration follows the same approach as 1. Use environment variables to configure the application running inside a container. Testcontainers builds the necessary container image and takes care of the container lifecycle.
_container = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(Image)
.WithNetwork(_network)
.WithPortBinding(HttpsPort, true)
.WithEnvironment("ASPNETCORE_URLS", "https://+")
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Path", _certificateFilePath)
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Password", _certificatePassword)
.WithEnvironment("ConnectionStrings__DefaultConnection", _connectionString)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(HttpsPort))
.Build();
This example sets up all necessary Docker resources to spin up a throwaway out-of-process test environment.