Introduction : Pourquoi les tests d'intégration avec Docker ?
Lors du développement d'applications modernes en Kotlin, notamment les microservices, il est important de vérifier comment les composants système interagissent entre eux et avec les dépendances externes (bases de données, files de messages, caches). Les tests unitaires isolent le code mais ne garantissent pas que l'application fonctionne correctement dans un environnement réel.
Les tests d'intégration avec Docker résolvent ce problème. Docker permet de lancer des conteneurs isolés avec les services nécessaires (PostgreSQL, Redis, Kafka) directement avant l'exécution des tests et de les détruire automatiquement après leur achèvement. Cela fournit :
- Reproductibilité : les tests s'exécutent toujours dans le même environnement.
- Isolation : les conteneurs n'affectent pas l'environnement de développement local.
- Vitesse : les conteneurs démarrent en secondes, pas en minutes.
Dans cet article, nous allons voir comment configurer les tests d'intégration pour une application Kotlin en utilisant Docker, la bibliothèque Testcontainers et Docker Compose. Vous obtiendrez des exemples de code prêts à l'emploi et des bonnes pratiques.
1. Configuration d'un projet Kotlin pour les tests d'intégration
1.1. Dépendances dans build.gradle.kts
Tout d'abord, ajoutez les dépendances nécessaires à votre fichier build.gradle.kts. Nous utiliserons JUnit 5, Testcontainers et le pilote PostgreSQL :
dependencies { // Dépendances principales de l'application implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("org.postgresql:postgresql:42.7.1")
// Tests testImplementation("org.junit.jupiter:junit-jupiter:5.10.1") testImplementation("org.testcontainers:testcontainers:1.19.3") testImplementation("org.testcontainers:postgresql:1.19.3") testImplementation("org.testcontainers:junit-jupiter:1.19.3")}
tasks.test { useJUnitPlatform()}1.2. Structure de base d'une classe de test
Créons une classe simple pour un test d'intégration qui lancera un conteneur PostgreSQL :
import org.junit.jupiter.api.Testimport org.testcontainers.containers.PostgreSQLContainerimport org.testcontainers.junit.jupiter.Containerimport org.testcontainers.junit.jupiter.Testcontainersimport java.sql.DriverManager
@Testcontainersclass UserRepositoryIntegrationTest {
companion object { @Container val postgres = PostgreSQLContainer<Nothing>("postgres:15-alpine") .withDatabaseName("testdb") .withUsername("test") .withPassword("test") }
@Test fun `should connect to database and execute query`() { val jdbcUrl = postgres.jdbcUrl val username = postgres.username val password = postgres.password
val connection = DriverManager.getConnection(jdbcUrl, username, password) val statement = connection.createStatement()
statement.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(100))") statement.execute("INSERT INTO users (name) VALUES ('Alice')")
val resultSet = statement.executeQuery("SELECT COUNT(*) FROM users") resultSet.next() val count = resultSet.getInt(1)
assert(count == 1) println("Test passed: found $count users") }}Remarque : l'annotation @Testcontainers gère automatiquement le cycle de vie du conteneur. Il démarre avant tous les tests de la classe et s'arrête après leur achèvement.
2. Tests avec plusieurs conteneurs (Docker Compose)
Dans les projets réels, plusieurs services sont souvent nécessaires simultanément : une base de données, Redis, Kafka. Testcontainers prend en charge Docker Compose, ce qui permet de décrire toute l'infrastructure dans un seul fichier.