Tests d'intégration Kotlin avec Docker : niveau avancé
Les tests d'intégration sont une étape clé pour vérifier l'interaction des composants d'une application. Dans l'écosystème Kotlin et Docker, ce processus atteint un nouveau niveau grâce à la conteneurisation. Dans cet article, nous aborderons des techniques avancées de configuration et d'exécution de tests d'intégration utilisant Docker, Testcontainers et Kotlin DSL.
1. Bases : Testcontainers avec Kotlin DSL
Testcontainers est une bibliothèque pour Java/Kotlin qui gère le cycle de vie des conteneurs Docker dans les tests. Pour Kotlin, un DSL pratique est disponible, simplifiant la configuration.
// build.gradle.ktsdependencies { testImplementation("org.testcontainers:testcontainers:1.20.0") testImplementation("org.testcontainers:postgresql:1.20.0") testImplementation("org.jetbrains.kotlin:kotlin-test")}Exemple de test avec 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 // Utilisons jdbcUrl dans DataSource println("Connecté à : $jdbcUrl") }}2. Exécution parallèle des conteneurs
Pour accélérer les tests, on peut exécuter les conteneurs en parallèle. Cependant, il est important de prendre en compte les conflits de ports. Utilisez des ports dynamiques et la synchronisation via 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("Port Redis1 : $port1, Port Redis2 : $port2") }}Utilisez Network pour l'isolation et waitingFor pour attendre que le conteneur soit prêt.
3. Docker Compose dans les tests
Pour les scénarios complexes (base de données + broker de messages + cache), il est pratique d'utiliser DockerComposeContainer. Il charge docker-compose.yml et gère tous les services.
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