Integrationstests von Kotlin mit Docker: Fortgeschrittenes Niveau
Integrationstests sind ein entscheidender Schritt zur Überprüfung der Interaktion von Anwendungskomponenten. Im Ökosystem von Kotlin und Docker erreicht dieser Prozess durch Containerisierung eine neue Ebene. In diesem Artikel werden wir fortgeschrittene Techniken zur Einrichtung und Durchführung von Integrationstests mit Docker, Testcontainers und Kotlin DSL behandeln.
1. Grundlagen: Testcontainers mit Kotlin DSL
Testcontainers ist eine Bibliothek für Java/Kotlin, die den Lebenszyklus von Docker-Containern innerhalb von Tests verwaltet. Für Kotlin ist ein praktischer DSL verfügbar, der die Konfiguration vereinfacht.
// build.gradle.ktsdependencies { testImplementation("org.testcontainers:testcontainers:1.20.0") testImplementation("org.testcontainers:postgresql:1.20.0") testImplementation("org.jetbrains.kotlin:kotlin-test")}Beispiel eines Tests mit PostgreSQL:
import org.testcontainers.containers.PostgreSQLContainerimport org.testcontainers.junit.jupiter.Containerimport org.testcontainers.junit.jupiter.Testcontainersimport org.junit.jupiter.api.Test
@Testcontainersclass UserRepositoryTest {
companion object { @Container val postgres = PostgreSQLContainer<Nothing>("postgres:15-alpine") .withDatabaseName("testdb") .withUsername("test") .withPassword("test") }
@Test fun `should connect to database`() { val jdbcUrl = postgres.jdbcUrl // Verwenden jdbcUrl in DataSource println("Connected to: $jdbcUrl") }}2. Paralleler Start von Containern
Um Tests zu beschleunigen, können Container parallel gestartet werden. Es ist jedoch wichtig, Portkonflikte zu berücksichtigen. Verwenden Sie dynamische Ports und Synchronisation über GenericContainer.
import org.testcontainers.containers.GenericContainerimport org.testcontainers.containers.Networkimport org.testcontainers.containers.wait.strategy.Waitimport java.time.Duration
class ParallelContainerTest {
companion object { private val network = Network.newNetwork()
private val redis1 = GenericContainer<Nothing>("redis:7-alpine") .withNetwork(network) .withNetworkAliases("redis1") .withExposedPorts(6379) .waitingFor(Wait.forListeningPort())
private val redis2 = GenericContainer<Nothing>("redis:7-alpine") .withNetwork(network) .withNetworkAliases("redis2") .withExposedPorts(6379) .waitingFor(Wait.forListeningPort())
init { redis1.start() redis2.start() } }
@Test fun `parallel containers test`() { val port1 = redis1.getMappedPort(6379) val port2 = redis2.getMappedPort(6379) println("Redis1 port: $port1, Redis2 port: $port2") }}Verwenden Sie Network zur Isolierung und waitingFor, um auf die Bereitschaft des Containers zu warten.
3. Docker Compose in Tests
Für komplexe Szenarien (Datenbank + Nachrichtenbroker + Cache) ist die Verwendung von DockerComposeContainer praktisch. Er lädt docker-compose.yml und verwaltet alle Dienste.
import org.testcontainers.containers.DockerComposeContainerimport java.io.File
class FullStackTest {
companion object { private val compose = DockerComposeContainer<Nothing>( File("src/test/resources/docker-compose.yml") ).apply { withExposedService("postgres_1", 5432) withExposedService("redis_1", 6379) withLocalCompose(true) start() } }
@Test fun `full integration test`() { val pgPort = compose.getServicePort("postgres_1", 5432) val redisPort = compose.getServicePort