xUnit 完全使用 FluentDocker 建立 MongoDB 的 Docker Image 和 Docker Container

前一篇文章「建立測試用 Container 前先建立 Docker Image - 使用 FluentDocker」是在 xUint 測試專案裡使用了 FluentDocker 和 Testcontainers 分別建立 Docker Image 與 Docker Container,這一篇就單純地展示完全使用 FluentDocker 來完成 Docker Image 和 Docker Container 的建立。

測試專案使用 FluentDocker

mariotoffia/FluentDocker: Use docker, docker-compose local and remote in tests and your .NET core/full framework apps via a FluentAPI

因為是 xUnit 測試專案,雖然也沒有用到套件裡所提供的類別、功能,但也還是安裝 Ductus.FluentDocker.XUint

至於 Ductus.FluentDocker.XUint 有提供了什麼給 xUnit 的功能,可以看以下的說明文件內容

 

修改 MongoFixture 檔案

原本的 MongoFixture.cs  檔案內容可以詳閱前一篇文章的內容「建立測試用 Container 前先建立 Docker Image - 使用 FluentDocker

原本裡面建立 Docker Image 的部分還是保持原來使用 docker-compose.yml 建立的方式,之後再來說明如果要全部改用 docker-compose.yml 建立 image 和 container  應該要怎麼做

在 MongoFixture.cs 裡新增 CreateMongoDockerContainer 方法

    private IContainerService _containerService;

    /// <summary>
    /// 建立測試用的 MongoDB Docker-container.
    /// </summary>
    private void CreateMongoDockerContainer()
    {
        var databaseSettings = TestSettingProvider.GetDatabaseSettings();
        DatabaseIp = "127.0.0.1";
        DatabasePort = databaseSettings.HostPort;
        var containerPort = databaseSettings.ContainerPort;

        var environmentName = TestSettingProvider.GetEnvironmentName();

        var environmentSettings = databaseSettings.EnvironmentSettings.Select(environmentSetting => $"{environmentSetting.Key}={environmentSetting.Value}").ToArray();

        // Create MongoDB Container
        this._containerService = new Builder().UseContainer()
                                              .DeleteIfExists(force: true)
                                              .UseImage($"{databaseSettings.Image}")
                                              .WithName($"{environmentName}")
                                              .ExposePort(hostPort: DatabasePort, containerPort: containerPort)
                                              .WaitForPort($"{containerPort}/tcp", TimeSpan.FromSeconds(10))
                                              .WithEnvironment(environmentSettings)
                                              .Build()
                                              .Start();
    }

將 MongoFixture.cs 原本繼承 IAsyncLifetime 介面拿掉,也需要移除 InitializeAsync 和 DisposeAsync 這兩個方法,然後改為繼承實做 IDisposable,測試執行完畢後要移除 docker container 則是要在 Dispose 方法裡處理,建立 Docker Image 與 Docker Container 會是在 建構式裡執行,調整後的程式內容如下: 

    private IContainerService _containerService;

    public MongoFixture()
    {
        CreateMongoDockerImage();
        this.CreateMongoDockerContainer();

        // FluentAssertions - Setup DateTime AssertionOptions
        SetupDateTimeAssertions();

        // MongoDb Class Mapping
        Mapping.RegisterClassMapping();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (disposing is false)
        {
            return;
        }

        this._containerService.Dispose();
        this._containerService= null;
    }

因為 MongoFixture 這個類別是給 MongoCollectonFixture 這個繼承 ICollecitonFixture<T>  所使用的,所以當整個使用 MongoCollectonFixture 都完成測試後,最後就會去執行 MongoFixture 類別解構式裡的內容,

[CollectionDefinition(nameof(MongoCollectionFixture))]
public class MongoCollectionFixture : ICollectionFixture<MongoFixture>
{
}

每個有需要使用到 MongoDB 的 RepositoryTests 測試類別,就要在類別上去標示使用 CollectionAttribute 並設定 MongoCollectionFixture

如果你對 xUnit 的 Shared Context  以及不同測試類別的建構式、解構式的作用還不是很瞭解的話,就請直接查看官方文件

 

完整的 MongoFixture 類別內容

以下是完整 MongoFixture.cs 程式內容

public class MongoFixture : IDisposable
{
    private IContainerService _containerService;

    public MongoFixture()
    {
        CreateMongoDockerImage();
        this.CreateMongoDockerContainer();

        // FluentAssertions - Setup DateTime AssertionOptions
        SetupDateTimeAssertions();

        // MongoDb Class Mapping
        Mapping.RegisterClassMapping();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (disposing is false)
        {
            return;
        }

        this._containerService.Dispose();
        this._containerService= null;
    }

    /// <summary>
    /// 建立測試用的 MongoDB Docker-image.
    /// </summary>
    private static void CreateMongoDockerImage()
    {
        // 使用 FluentDocker (https://github.com/mariotoffia/FluentDocker)
        // 透過 FluentDocker 的 UseCompose 功能執行 docker-compose.yml
        // 執行 docker-compose 就會建立 MongoDB docker-image
        var dockerComposeFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Docker", "Mongo", "docker-compose.yml");
        using var svc = new Builder().UseContainer().UseCompose().FromFile(dockerComposeFile).RemoveOrphans().Build().Start();
    }

    /// <summary>
    /// 建立測試用的 MongoDB Docker-container.
    /// </summary>
    private void CreateMongoDockerContainer()
    {
        var databaseSettings = TestSettingProvider.GetDatabaseSettings();
        DatabaseIp = "127.0.0.1";
        DatabasePort = databaseSettings.HostPort;
        var containerPort = databaseSettings.ContainerPort;

        var environmentName = TestSettingProvider.GetEnvironmentName();

        var environmentSettings = databaseSettings.EnvironmentSettings.Select(environmentSetting => $"{environmentSetting.Key}={environmentSetting.Value}").ToArray();

        // Create MongoDB Container
        this._containerService = new Builder().UseContainer()
                                              .DeleteIfExists(force: true)
                                              .UseImage($"{databaseSettings.Image}")
                                              .WithName($"{environmentName}")
                                              .ExposePort(hostPort: DatabasePort, containerPort: containerPort)
                                              .WaitForPort($"{containerPort}/tcp", TimeSpan.FromSeconds(10))
                                              .WithEnvironment(environmentSettings)
                                              .Build()
                                              .Start();
    }

    /// <summary>
    /// FluentAssertions - Setup DateTime AssertionOptions
    /// </summary>
    private static void SetupDateTimeAssertions()
    {
        // FluentAssertions 設定 : 日期時間使用接近比對的方式,而非完全一致的比對
        AssertionOptions.AssertEquivalencyUsing(options =>
        {
            options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromMilliseconds(1000)))
                   .WhenTypeIs<DateTime>();

            options.Using<DateTimeOffset>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromMilliseconds(1000)))
                   .WhenTypeIs<DateTimeOffset>();

            return options;
        });
    }
}

可以看到這邊修改後的內容就已經沒有使用到 Testcontainers 了,所以你想要單純只使用一種 Docker for Test 的套件就可以參考這一篇的方式來試試看。

以上

分享