.net-corexunittestcontainersxunit.net

Xunit TestContainers single server per class with fresh database per test


I'm trying to initiate a single TestContainer instance that creates sql server per test class. Then before every test a fresh database is created and migrated and after every test database is deleted.

TestContainer fixture is defined with IAsyncLifetime, so is the test class, because they rely on async methods.

TestClass defines single TestContainerFixture per class with IClassFixture. This seems to be working fine, since TestContainerFixtures InitializeAsync() gets called only once.

TestClass also uses IAsyncLifetime and TestClasses InitializeAsync() is run before every test. This works, but the problem is that I am unable to create database with unique name, because Guid.NewGuid().ToString() always returns identical result. Why is that? Am I missing some crucial information about how xunit/.net-core handles parallel tests / threading here?

Test class is like this:

public class TestClass: IClassFixture<TestContainerFixture>, IAsyncLifetime
{
    private readonly TestContainerFixture _testContainerFixture;

    public TestClass(TestContainerFixture fixture)
    {
        _testContainerFixture = fixture;
    }

    public async Task InitializeAsync()
    {
        // this guid will always be identical, that is the main problem
        var guid = Guid.NewGuid().ToString();

        var data = await _testContainerFixture.SetupDatabaseAsync(guid);
    }

    public async Task DisposeAsync()
    {
        await _testContainerFixture.DropDatabaseAsync();
    }
}

TestContainerFixture is defined like this:

public class TestContainerFixture : IAsyncLifetime
{
    private string _databaseName { get; set; } = String.Empty;

    private string _connectionString { get; set; } = String.Empty;

    private MsSqlContainer _databaseContainer { get; set; } = default!;

    public TestContainerFixture()
    {
    }

    public virtual async Task InitializeAsync()
    {
        _databaseContainer = new MsSqlBuilder()
                                .WithAutoRemove(autoRemove: true)
                                .WithExposedPort(54465)
                                .Build();

        await _databaseContainer.StartAsync();
    }

    public async Task<(IOptionsMonitor<Config>, IUnitOfWork)> SetupDatabaseAsync(string dbname)
    {
        _databaseName = dbname;
        // this commented out code also produces identical guids between tests
        //_databaseName = "Test" + Guid.NewGuid().ToString().Replace("-", "");
        await _databaseContainer.ExecScriptAsync($"create database [{dbname}]");
        _connectionString = _databaseContainer.GetConnectionString().Replace("Database=master", $"Database={dbname}");
    }

    public async Task DisposeAsync() => await _databaseContainer.StopAsync();
}

Solution

  • The solution is to remove databaseName and connectionString from TestContainerFixture properties and pass them around as parameters all the way from TestClass IAsyncLifeTime.

    Maybe TestClass wasn’t creating identical GUIDs at all, or maybe I was confused as TestContainerFixture had only one instance and the private properties weren't updating. I used ITestOutputHelper to figure this out.

    Have to test this out again sometime, but it’s working now.