Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to automate DB setup of Entity Framework's Code First in Testcontainers for testing?

In order to setup the Testcontainer's MsSqlTestcontainer for testing, I copied the script emitted by dotnet ef migrations script and planted it in the unit-test's setup (actual code doesn't :

void PrepareDb(MsSqlTestcontainer dbTestContainer){
    dbTestContainer.ExecScriptAsync(@"CREATE TABLE [DbName] ( /* ... */ )");
}

Is there a way to automate it, for instance if the DB model ever changes, and wire-up e.g. MyDbContext straight to the Testcontainer's logic?

I was considering passing the MyDbContext code into the container and run dotnet ef migrations script inside of it, but I'm not sure how much it worth the effort (and I need to use a container that already has dotnet installed, which is another complication..).

That's the dbTestContainer setup, FWIW:

var dbTestContainer = new TestcontainersBuilder<MsSqlTestcontainer>()
    .WithDatabase(new MsSqlTestcontainerConfiguration { Password = "whatever secret" })
    .WithImage("mcr.microsoft.com/azure-sql-edge")
    .WithWaitStrategy(Wait.ForUnixContainer())
    .Build()
like image 939
Tar Avatar asked Oct 22 '25 03:10

Tar


1 Answers

I had the same problem and I managed to solve it, at least for my own basic needs. In case you still need a solution, here is what I did.

Note: If anyone knows a better approach or spots any flaws below, I would be very happy to hear about it.

I have a IntegrationTestWebApplicationFactory which I use to do the usual configuration for my integration tests. As Pavel already pointed out, you can run the migrations programmatically before the tests start. For this, my IntegrationTestWebApplicationFactoryimplements the IAsyncLifetime interface of XUnit, which I am using for testing. This interface requires you to implement InitializeAsync and DisposeAsync methods. Inside of InitializeAsync I run the await dbContext.Database.MigrateAsync(); command.

Here is the full code of my IntegrationTestWebApplicationFactory class:

public class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
    private readonly TestcontainerDatabase _container;

    public IntegrationTestFactory()
    {
        _container = new TestcontainersBuilder<MsSqlTestcontainer>()
            .WithDatabase(new MsSqlTestcontainerConfiguration
            {
                Username = "sa",
                Database = "WeatherApp",
                Password = "2@LaiNw)PDvs^t>L!Ybt]6H^%h3U>M",
            })
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            .WithCleanUp(true)
            .Build();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddDbContext<DemoDbContext>(options => { options.UseSqlServer(_container.ConnectionString); });
        });
    }

    public async Task InitializeAsync()
    {
        await _container.StartAsync();
        using var scope = Services.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<DemoDbContext>();
        await dbContext.Database.MigrateAsync();
    }

    public new async Task DisposeAsync() => await _container.DisposeAsync();
}

And this is how I used it in my integration tests:

    [Theory]
    [InlineAutoData]
    public async Task GettingWeatherForecastReturnsOkay(WeatherForecast expectedForecast)
    {
        var client = _integrationTestFactory.CreateClient();

        // insert into db what you want to assert
        await client.PostAsJsonAsync("WeatherForecast", expectedForecast);
        
        // read from db
        var forecasts = await client.GetFromJsonAsync<List<WeatherForecast>>("WeatherForecast");

        // do asserts or whatever..
        forecasts.Should().NotBeEmpty();
        forecasts.Should().ContainEquivalentOf(expectedForecast);
    }
like image 109
Pete Avatar answered Oct 25 '25 13:10

Pete