Last active
October 1, 2024 09:02
-
-
Save dj-nitehawk/04a78cea10f2239eb81c958c52ec84e0 to your computer and use it in GitHub Desktop.
Integration testing with TestContainers & AppFixture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Testcontainers.MongoDb; | |
public class Sut : AppFixture<Program> | |
{ | |
const string Database = "TestingDB"; | |
const string RootUsername = "root"; | |
const string RootPassword = "password"; | |
MongoDbContainer _container = null!; | |
protected override async Task PreSetupAsync() | |
{ | |
//this function is only ever called once before the WAF/SUT instance is created & cached. | |
//every derived AppFixture type will cache just one instance of a WAF/SUT. | |
//more info: https://fast-endpoints.com/docs/integration-unit-testing#app-fixture | |
_container = new MongoDbBuilder() | |
.WithImage("mongo") | |
.WithUsername(RootUsername) | |
.WithPassword(RootPassword) | |
.WithCommand("mongod") | |
.Build(); | |
await _container.StartAsync(); | |
} | |
protected override void ConfigureApp(IWebHostBuilder b) | |
{ | |
b.ConfigureAppConfiguration( | |
c => | |
{ | |
c.AddInMemoryCollection( | |
new Dictionary<string, string?> | |
{ | |
{ "Mongo:Host", _container.Hostname }, | |
{ "Mongo:Port", _container.GetMappedPublicPort(27017).ToString() }, | |
{ "Mongo:DbName", Database }, | |
{ "Mongo:UserName", RootUsername }, | |
{ "Mongo:Password", RootPassword } | |
}); | |
}); | |
} | |
protected override async Task TearDownAsync() | |
{ | |
//await _container.DisposeAsync(); | |
//NOTE: there's no need to dispose the container here as it will be automatically disposed by testcontainers pkg when the test run finishes. | |
// this is especially true if this AppFixture is used by many test-classes with WAF caching enabled. | |
// so, in general - containers don't need to be explicitly disposed, unless you disable WAF caching. | |
} | |
} |
@TheBenda anytime after _container.StartAsync()
should be fine.
@dj-nitehawk after some testing I found this solution. :)
@TheBenda from my testing it seems that PreSetupAsync
is running once per assembly, SetupAsync
runs once per test fixture, hence running migrations in SetupAsync
can lead to slower tests and conflicts due to parallelization.
I did it like this:
public class Sut : AppFixture<Program>
{
private PostgreSqlContainer? postgreSqlContainer;
protected override async Task PreSetupAsync()
{
postgreSqlContainer = new PostgreSqlBuilder()
.WithImage("postgres:16")
.WithDatabase("db")
.WithUsername("db")
.WithPassword("db")
.Build();
await postgreSqlContainer.StartAsync();
var connectionString = postgreSqlContainer.GetConnectionString();
// this is optional, we're using environment variables for connection string
Environment.SetEnvironmentVariable("DB_CONNECTION_STRING", connectionString);
// in pre PreSetupAsync Services is not initialized yet,
// hence need to create temporary service provider to run migrations
// running migrations in SetupAsync is not viable as setup runs for each fixture
// so if tests are running in parallel, migrations run in parallel too and that leads to complex situations
var serviceCollection = new ServiceCollection();
// custom extension method, so that the DbContext options would match test and actual program
serviceCollection.AddMyDbContext();
using (var serviceProvider = serviceCollection.BuildServiceProvider())
{
using (var dbContext = serviceProvider.GetRequiredService<MyDbContext>())
{
await dbContext.Database.MigrateAsync();
}
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, thanks for the example. Imagine running a DB test container, where/when would I run the migrations? I guess in SetupAsync?