Introduction : Pourquoi des tests sans mocks, c'est comme un café sans caféine ?
\n\nBonjour, collègue ! Si tu codes en Kotlin et que tu veux que ton code ne casse pas à chaque deuxième commit, tôt ou tard, tu devras passer par les tests. Et dans les tests, par les mock-objets et les fixtures. Sans eux, les tests unitaires deviennent des tests d'intégration : tu ne testes pas une seule classe, mais toute une chaîne de dépendances. Et si la base de données tombe ou que l'API renvoie une 500, le test échoue, même si ton code est parfait.
\n\nDans cet article, nous allons voir comment utiliser correctement les mock-objets et fixtures dans les tests Kotlin pour que tes tests soient rapides, fiables et te disent la vérité, pas « tout est cassé, répare ». C'est parti !
\n\nQu'est-ce qu'un mock et pourquoi en a-t-on besoin en Kotlin ?
\n\nUn mock-objet est une imitation d'un objet réel. Au lieu de lancer tout un serveur ou une base de données, on crée un « leurre » qui se comporte comme on le souhaite. Cela permet d'isoler le code testé du monde extérieur.
\n\nConcepts clés
\n- \n
- Mock : un objet dont on contrôle le comportement des méthodes. On peut définir ce qu'il retourne et vérifier s'il a été appelé. \n
- Stub : un cas particulier de mock qui ne fait que retourner des valeurs prédéfinies. Il ne vérifie pas les appels. \n
- Fixture : un ensemble de données ou d'états préparés à l'avance, nécessaires à l'exécution du test. Par exemple, un utilisateur de test ou des paramètres de base de données. \n
Dans le monde Kotlin, deux acteurs principaux se distinguent : MockK (natif pour Kotlin) et Mockito (populaire en Java). Nous utiliserons MockK, car il s'intègre mieux avec les coroutines, les fonctions d'extension et les classes sealed.
\n\nConfiguration du projet : ajouter MockK et JUnit 5
\n\nPour commencer, ajoutons les dépendances dans build.gradle.kts :
dependencies {\n testImplementation("io.mockk:mockk:1.13.8")\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n}\n\n\nMaintenant, on peut écrire des tests. Créons un service simple et testons-le avec des mocks.
\n\nPremier exemple : moquer un repository
\n\nImaginons que nous ayons un UserService qui dépend d'un UserRepository :
class UserService(private val repository: UserRepository) {\n fun getUserName(id: Long): String {\n val user = repository.findById(id)\n ?: throw IllegalArgumentException("User not found")\n return user.name\n }\n}\n\n\nÉcrivons maintenant un test en utilisant un mock-objet pour le repository :
\n\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\n\nclass UserServiceTest {\n\n private val repository = mockk<UserRepository>()\n private val service = UserService(repository)\n\n @Test\n fun `should return user name when user exists`() {\n // Arrange\n every { repository.findById(1L) } returns User(1L, "Alice")\n\n // Act\n val name = service.getUserName(1L)\n\n // Assert\n assertEquals("Alice", name)\n verify { repository.findById(1L) }\n }\n\n @Test\n fun `should throw exception when user not found`() {\n // Arrange\n every { repository.findById(any()) } returns null\n\n // Act & Assert\n assertFailsWith<IllegalArgumentException> {\n service.getUserName(99L)\n }\n }\n}\n\n\nRemarque : every définit le comportement du mock, verify vérifie que la méthode a été appelée. C'est la base des mock-objets et fixtures dans les tests Kotlin.
Fixtures : préparer les données à l'avance
\n\nQuand on a beaucoup de tests, dupliquer la création de données est une mauvaise pratique. Les fixtures viennent à la rescousse. En Kotlin, il est pratique