しおしお

IntelliJ IDEAのことなんかを書いてます

KotestでTestcontainersを使ってみる

KotestTestcontainersを簡単に使う方法になります。

KotestのTestcontainers拡張を追加

KotestにはTestcontainers用の拡張ライブラリが用意されているので、それを依存に追加します。

build.gradleの場合

testImplementation 'io.kotest.extensions:kotest-extensions-testcontainers:1.1.1'

pom.xmlの場合

<dependency>
    <groupId>io.kotest.extensions</groupId>
    <artifactId>kotest-extensions-testcontainers</artifactId>
    <version>1.1.1</version>
    <scope>test</scope>
</dependency>

デフォルト構成Testcontainerを使ってみる

テストコード

WireMockのコンテナをTestcontainersで起動するテストコードになります。 TestContainerExtensionにimageを指定して、installすることでコンテナを上げることができます。

class TestContainerExample : FreeSpec({
    val container = install(TestContainerExtension("wiremock/wiremock:latest")) {
        withExposedPorts(8080)
    }

    (1..5).forEach {
        "test $it" {
            println("test $it")
            println("container.containerId = ${container.containerId}")
            println("container.getMappedPort(8080) = ${container.getMappedPort(8080)}")
        }
    }

})

実行結果

実行結果を見ると、コンテナが起動され公開されたポートがホスト側のポートにマッピングされていることが確認できます。 デフォルトでは、全てのテストケースで同じコンテナを使いまわしするようです。(Spec単位でコンテナが管理されます)

test 1
container.containerId = 74716ee4e1bf41bb73012c95648698f2a22b06e1ff670dbc187096735fa39bb2
container.getMappedPort(8080) = 49190
test 2
container.containerId = 74716ee4e1bf41bb73012c95648698f2a22b06e1ff670dbc187096735fa39bb2
container.getMappedPort(8080) = 49190
test 3
container.containerId = 74716ee4e1bf41bb73012c95648698f2a22b06e1ff670dbc187096735fa39bb2
container.getMappedPort(8080) = 49190
test 4
container.containerId = 74716ee4e1bf41bb73012c95648698f2a22b06e1ff670dbc187096735fa39bb2
container.getMappedPort(8080) = 49190
test 5
container.containerId = 74716ee4e1bf41bb73012c95648698f2a22b06e1ff670dbc187096735fa39bb2
container.getMappedPort(8080) = 49190

テストケース単位でコンテナを使い捨てる構成にしてみる

テストコード

TestContainerExtensionLifecycleMode.EveryTestを指定することで、テストケース単位にコンテナが起動されクリーンな状態でテストが実行できるようになります。

class TestContainerExample : FreeSpec({
    val container = install(TestContainerExtension("wiremock/wiremock:latest", LifecycleMode.EveryTest)) {
        withExposedPorts(8080)
    }

    (1..5).forEach {
        "test $it" {
            println("test $it")
            println("container.containerId = ${container.containerId}")
            println("container.getMappedPort(8080) = ${container.getMappedPort(8080)}")
        }
    }

})

実行結果

実行結果を見るとテストごとにコンテナIDが変わっていることが確認できるので、LifecycleModeの指定が効いていることが確認できます。

test 1
container.containerId = 74bd2ffd8304eebbdb57281b5e7bd818f379036caf314fe36714ba877edd80f3
container.getMappedPort(8080) = 49192
test 2
container.containerId = a278dd9a16a066e1dd0beb5785c4b61ec3088c96c89d9bbc8deedb0f2cd7d99c
container.getMappedPort(8080) = 49193
test 3
container.containerId = 6b47542b908d6da7b2a5a9aad7997840b0c626fd08851c1f0140691629e9f861
container.getMappedPort(8080) = 49194
test 4
container.containerId = 54dedc990cde75c608c0491f1d12d4cc7ee779a0e24587665341929f3a5b3108
container.getMappedPort(8080) = 49195
test 5
container.containerId = f4237335e4d9bb167dc07b1b466f23b2e22101c1d0dbc3470e8092688c034e7d
container.getMappedPort(8080) = 49196

データベースを扱ってみる

データベースを扱う際には、Testcontainersの使用したいデータベースのモジュールを使用してコンテナを上げるのが簡単です。 テストコードで、データベースにアクセスしたい場合には、 JdbcTestContainerExtension を使用することで立ち上げたコンテナに対する接続を持ったHikariDataSourceを取得することができます。 HikariDataSourceがあれば、データのセットアップ系などの処理も簡単に行えるので良さそうですね。

build.gradle

  testImplementation "org.testcontainers:postgresql:1.16.3"
  testRuntimeOnly 'org.postgresql:postgresql:42.3.1'

テストコード

class TestContainerExample : FreeSpec({
    val postgresql = PostgreSQLContainer<Nothing>("postgres:9.6.24-stretch")
    val datasource = install(JdbcTestContainerExtension(postgresql)) {
        poolName = "test-pool"
    }

    "test" {
        datasource.connection.metaData.databaseProductVersion shouldBe "9.6.24"
    }
})

おわり。