Search code examples
c#asp.net-coreintegration-testing

ASP.NET Core Integration tests with dotnet-testcontainers


I am trying to add some integration tests for a aspnetcore v6 webapi following the docs - https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#aspnet-core-integration-tests.

My webapi database is SQLServer. I want the tests to be run against an actual SQLServer db and not in-memory database. I came across dotnet-testcontainers - https://github.com/HofmeisterAn/dotnet-testcontainers and thinking of using this so I do not need to worry about the resetting the db as the container is removed once test is run.

So this is what I plan to do:

  1. Start-up a SQLServer testcontainer before the test web host is started. In this case, the test web host is started using WebApplicationFactory. So the started wen host has a db to connect with. Otherwise the service start will fail.
  2. Run the test. The test would add some test data before its run.
  3. Then remove the SQLServer test container along with the Disposing of test web host.

This way the I can start the test web host that connects to a clean db running in a container, run the tests.

Does this approach sound right? OR Has someone used dotnet-testcontainers to spin up a container for their application tests and what approach worked.


Solution

  • I wrote about this approach here.

    You basically need to create a custom WebApplicationFactory and replace the connection string in your database context with the one pointing to your test container.

    Here is an example, that only requires slight adjustments to match the MSSQL docker image.

    public class IntegrationTestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>, IAsyncLifetime
        where TProgram : class where TDbContext : DbContext
    {
        private readonly TestcontainerDatabase _container;
    
        public IntegrationTestFactory()
        {
            _container = new TestcontainersBuilder<PostgreSqlTestcontainer>()
                .WithDatabase(new PostgreSqlTestcontainerConfiguration
                {
                    Database = "test_db",
                    Username = "postgres",
                    Password = "postgres",
                })
                .WithImage("postgres:11")
                .WithCleanUp(true)
                .Build();
        }
    
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureTestServices(services =>
            {
                services.RemoveProdAppDbContext<TDbContext>();
                services.AddDbContext<TDbContext>(options => { options.UseNpgsql(_container.ConnectionString); });
                services.EnsureDbCreated<TDbContext>();
            });
        }
    
        public async Task InitializeAsync() => await _container.StartAsync();
    
        public new async Task DisposeAsync() => await _container.DisposeAsync();
    }
    

    And here are the extension methods to replace and initialize your database context.

    public static class ServiceCollectionExtensions
    {
        public static void RemoveDbContext<T>(this IServiceCollection services) where T : DbContext
        {
            var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<T>));
            if (descriptor != null) services.Remove(descriptor);
        }
    
        public static void EnsureDbCreated<T>(this IServiceCollection services) where T : DbContext
        {
            var serviceProvider = services.BuildServiceProvider();
    
            using var scope = serviceProvider.CreateScope();
            var scopedServices = scope.ServiceProvider;
            var context = scopedServices.GetRequiredService<T>();
            context.Database.EnsureCreated();
        }
    }