Tests en Kotlin : unitaires, d'intégration, E2E de A à Z
\n\nLe développement moderne en Kotlin nécessite une approche systématique des tests. Que vous écriviez un backend avec Ktor ou une application mobile Android, une couverture de code de qualité par des tests est la clé de la stabilité et de la prévisibilité du projet. Dans cet article, nous aborderons trois niveaux de tests : les tests unitaires, les tests d'intégration et les tests de bout en bout (E2E). Vous découvrirez quels outils utiliser, comment écrire des tests efficaces en Kotlin et éviter les erreurs courantes.
\n\n1. Tests unitaires en Kotlin : les bases fondamentales
\n\nLes tests unitaires vérifient le fonctionnement de fonctions, classes ou méthodes individuelles isolées des dépendances externes. En Kotlin, on utilise le plus souvent le framework JUnit 5 combiné à la bibliothèque de mocks MockK.
\n\nConfiguration du projet
\n\nAjoutez les dépendances dans build.gradle.kts :
dependencies {\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n testImplementation("io.mockk:mockk:1.13.8")\n}\n\n\nExemple de test unitaire
\n\nSupposons que nous ayons un service simple pour calculer une remise :
\n\nclass DiscountService {\n fun applyDiscount(price: Double, discountPercent: Int): Double {\n require(discountPercent in 0..100) { "Le pourcentage de remise doit être compris entre 0 et 100" }\n return price * (100 - discountPercent) / 100\n }\n}\n\n\nTest pour ce service :
\n\nimport org.junit.jupiter.api.Assertions.*\nimport org.junit.jupiter.api.Test\nimport org.junit.jupiter.api.assertThrows\n\nclass DiscountServiceTest {\n\n private val service = DiscountService()\n\n @Test\n fun `applyDiscount calcule correctement une remise de 50 pourcents`() {\n val result = service.applyDiscount(1000.0, 50)\n assertEquals(500.0, result, 0.001)\n }\n\n @Test\n fun `applyDiscount lève une exception pour un pourcentage invalide`() {\n assertThrows<IllegalArgumentException> {\n service.applyDiscount(100.0, 150)\n }\n }\n}\n\n\nConseil : Utilisez assertThrows pour vérifier les exceptions et nommez les tests avec des chaînes claires — cela améliore la lisibilité des rapports.
Mocking avec MockK
\n\nLorsque votre classe dépend d'autres composants (par exemple, un repository ou une API), utilisez des mocks :
\n\nclass OrderService(private val paymentGateway: PaymentGateway) {\n fun processOrder(amount: Double): String {\n if (paymentGateway.charge(amount)) {\n return "Paiement réussi"\n }\n return "Erreur de paiement"\n }\n}\n\n// Test\n@Test\nfun `processOrder retourne un succès lors d'un paiement réussi`() {\n val mockGateway = mockk<PaymentGateway>()\n every { mockGateway.charge(any()) } returns true\n\n val service = OrderService(mockGateway)\n val result = service.processOrder(500.0)\n\n assertEquals("Paiement réussi", result)\n verify { mockGateway.charge(500.0) }\n}\n\n\n2. Tests d'intégration : vérification de l'interaction des composants
\n\nLes tests d'intégration vérifient comment différentes parties du système fonctionnent ensemble : base de données, API externes, système de fichiers. Pour Kotlin, Testcontainers est parfait — une bibliothèque pour lancer des conteneurs Docker pendant les tests.
\n\nExemple avec une base de données PostgreSQL
\n\nAjoutez les dépendances :
\n\ntestImplementation("org.testcontainers:testcontainers:1.19.3")\ntestImplementation("org.testcontainers:postgresql:1.19.3")\n\n\nÉcrivons un test pour la couche DAO :
\n\nclass UserRepositoryIntegrationTest {\n\n companion object {\n private val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply {\n withDatabaseName("testdb")\n withUsername("test")\n withPassword("test")\n start()\n