MockK est un framework Kotlin-first pour créer des objets mock, conçu spécifiquement pour l'écosystème Kotlin en tenant compte de ses caractéristiques linguistiques : coroutines, fonctions d'extension, data class et sealed class. Contrairement à Mockito, qui a été porté de Java vers Kotlin, MockK a été conçu dès l'origine pour la syntaxe Kotlin et ne nécessite pas de plugins supplémentaires pour fonctionner avec les classes final. Selon MockK.io, la bibliothèque est utilisée dans plus de 40 % des projets Kotlin avec des tests unitaires.
Points clés
MockK est une bibliothèque pour créer des objets mock, écrite en Kotlin et optimisée pour sa syntaxe. Elle résout les mêmes problèmes que Mockito — isoler le code testé des dépendances — mais le fait en utilisant des constructions spécifiques à Kotlin : lambdas, DSL, reified generics et fonctions suspend.
Le principal avantage de MockK par rapport aux solutions portées est la prise en charge native de Kotlin. Dans Mockito, simuler une classe final nécessite un opt-in (mockito-inline), et les méthodes statiques nécessitent mockStatic. MockK le supporte par défaut, car les classes Kotlin sont final par défaut, et contourner cette limitation est intégré dans l'architecture de la bibliothèque.
La version 1.13.12 (2024) est une version stable prenant en charge Kotlin 2.0, le compilateur K2 et les projets multiplateformes (KMP). MockK fonctionne également avec Kotlin/Native et Kotlin/JS, ce qui en fait le seul choix pour les projets KMP où ni Mockito ni EasyMock ne sont applicables.
MockK est conçu en tenant compte des spécificités de Kotlin et utilise les fonctionnalités du langage — reified generics, DSL avec lambdas, fonctions inline — pour fournir une API concise et type-safe sans sacrifier les performances.
Le mécanisme de MockK est basé sur l'instrumentation de bytecode via la bibliothèque ByteBuddy (comme Mockito), mais l'enveloppe dans un DSL compatible Kotlin. Au lieu des chaînes when().thenReturn(), MockK utilise des blocs lambda every { } et coEvery { } qui ressemblent à une extension naturelle du langage. Sous le capot, MockK intercepte l'appel dans le lambda, analyse la méthode et les arguments via la réflexion et les fait correspondre aux règles de stubbing enregistrées.
Le bloc every { mock.method() } returns value se lit comme « à chaque fois que la méthode est appelée, retourner la valeur. » Cette syntaxe déclarative est plus proche du style Kotlin et élimine la confusion sur l'ordre des arguments dans when(). Grâce aux reified generics de Kotlin, le type du mock est déduit automatiquement sans spécifier explicitement la classe.
val repository = mockk<UserRepository>()
// Stubbing : chaque appel à findById(1) retourne l'utilisateur
every { repository.findById(1) } returns User("Alice")
// Appel et vérification
val result = repository.findById(1)
assertEquals("Alice", result.name)
Contrairement à Mockito, où chaque méthode doit être configurée explicitement, MockK prend en charge le relaxed mock — un mock qui retourne des valeurs par défaut « raisonnables » pour toute méthode : une liste vide pour List, 0 pour Int, une chaîne vide pour String. Cela réduit considérablement la quantité de code de configuration.
// Relaxed mock — toutes les méthodes retournent des valeurs par défaut
val api = mockk<ApiService>(relaxed = true)
// Ne nécessite pas de stubbing — retourne une liste vide
println(api.getUsers()) // []
MockK propose plusieurs façons de créer des objets mock : mockk<T>() pour les mocks stricts (chaque méthode doit être configurée explicitement), mockk<T>(relaxed = true) pour les mocks relaxés, et spyk(obj) pour créer un espion sur un objet réel.
| Fonction | Type | Comportement sans stubbing |
|---|---|---|
| mockk() | Mock strict | Lève une exception lors de l'appel d'une méthode non configurée |
| mockk(relaxed = true) | Mock relaxé | Retourne la valeur par défaut |
| spyk() | Espion | Appelle la méthode réelle si aucun stub n'est configuré |
| slot() | Argument Captor | Capture l'argument pour vérification |
Le choix entre mock strict et relaxé dépend du contexte. Un mock strict garantit que le test n'utilise pas de méthodes dont le comportement n'est pas défini — cela augmente la fiabilité. Un mock relaxé est pratique pour le prototypage rapide de tests où toutes les dépendances ne sont pas importantes. En pratique, il est recommandé de commencer avec un mock strict et de passer au relaxé uniquement lorsque le stubbing prend plus de lignes que le test lui-même.
Le bloc every est la construction centrale de stubbing dans MockK. Dans le lambda, un appel de méthode avec des arguments spécifiques est décrit, puis une valeur est retournée via returns, une exception est levée via throws, ou une réponse est calculée via answers.
MockK prend en charge tous les scénarios nécessaires aux tests : retourner une valeur, lever une exception, calculer une réponse basée sur les arguments et plusieurs réponses dans l'ordre (séquence d'appels).
// Retourner une valeur
every { repo.findById(1) } returns User("Alice")
// Lever une exception
every { repo.findById(999) } throws NotFoundException()
// Réponse dynamique
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Séquence de réponses
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
Verify dans MockK est similaire à Mockito.verify() dans son but, mais utilise le DSL Kotlin : verify { mock.method() }. Pour les fonctions suspend, on utilise coVerify { mock.suspendMethod() }, qui fonctionne correctement avec les coroutines et ne nécessite pas d'exécuteur spécial.
MockK prend en charge les mêmes modificateurs que Mockito : exactly(1), atLeast(2), atMost(5), wasNot(Called). La syntaxe est minimaliste — le modificateur est passé comme premier argument dans verify { }.
// Vérification : méthode appelée exactement 1 fois
verify(exactly = 1) { repo.save(any()) }
// Vérifier l'ordre des appels
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify pour les fonctions suspend
coVerify { api.fetchUsers() }
Pour la vérification des arguments, on utilise slot() — un analogue d'ArgumentCaptor. Un slot est déclaré avant l'appel, passé à every ou verify, et après l'exécution du test contient la valeur capturée.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockK fournit les annotations @MockK et @RelaxedMockK pour créer des mocks via l'initialisation dans JUnit 5. L'extension MockKExtension crée automatiquement des mocks avant chaque test et nettoie après — similaire à MockitoExtension, mais avec la prise en charge du mode relaxé.
L'annotation @InjectMockKs (ou l'alternative @MockK avec création explicite d'objet) injecte les mocks dans l'instance testée. Cela réduit le code répétitif et rend le code de test plus propre.
@ExtendWith(MockKExtension::class)
class UserServiceTest {
@MockK
lateinit var repository: UserRepository
@InjectMockKs
lateinit var service: UserService
@Test
fun `getUser returns user from repository`() {
every { repository.findById(1) } returns User("Alice")
assertEquals("Alice", service.getUser(1)?.name)
}
}
Le choix entre MockK et Mockito dépend de la composition de l'équipe et du type de projet. Mockito a un écosystème plus vaste, plus d'exemples et d'intégrations, mais MockK offre une syntaxe Kotlin plus propre et une prise en charge native des fonctionnalités du langage. Pour les nouveaux projets Kotlin, MockK est recommandé comme solution plus idiomatique.
| Critère | MockK | Mockito |
|---|---|---|
| Syntaxe | DSL Kotlin (every, verify) | Style Java (when, thenReturn) |
| Coroutines | coEvery, coVerify (natif) | Nécessite des bibliothèques supplémentaires |
| Classe final | Pris en charge par défaut | Nécessite mockito-inline |
| KMP | Pris en charge | Non pris en charge |
| Relaxed mock | Intégré | Pas d'équivalent |
| Popularité | Croissante dans la communauté Kotlin | Dominante dans les projets Java et hybrides |
Pour les projets en Kotlin pur (sans classes Java), MockK est préférable : moins de code répétitif, prise en charge native des coroutines, pas de surprises avec les classes final. Pour les projets hybrides ou les équipes ayant une expérience Java, Mockito reste une option viable — les deux bibliothèques peuvent être utilisées dans le même projet via différents modules. Lors de la migration de Mockito vers MockK, il suffit de remplacer les annotations @Mock par @MockK et de réécrire les blocs when().thenReturn() au format every { }.
Questions fréquentes
Relaxed mock retourne des valeurs par défaut pour toutes les méthodes non configurées (liste vide, 0, null) sans lever d'exceptions. Un mock normal (strict) nécessite un stubbing explicite de chaque méthode — sinon le test échoue. Le relaxed mock est pratique pour les tests rapides, le strict pour les tests fiables.
MockK prend en charge la simulation de fonctions d'extension via mockkStatic(). C'est possible car les fonctions d'extension en Kotlin sont des méthodes statiques avec le récepteur comme premier paramètre. Pour chaque fonction d'extension, vous devez spécifier la classe dans laquelle elle est déclarée.
Oui, MockK prend en charge Kotlin Multiplatform (KMP) pour le code commun. Sur les plateformes JVM, Native et JS, vous pouvez utiliser l'API commune : mockk(), every, verify. Cela fait de MockK le seul choix pour les projets KMP où Mockito ne fonctionne pas.
Utilisez verifySequence { } — un bloc où les appels sont spécifiés strictement dans l'ordre attendu. Si l'ordre réel diffère, verifySequence lèvera une exception indiquant le premier appel non conforme.
Oui, techniquement c'est possible, mais déconseillé. Des conflits peuvent survenir au niveau de l'instrumentation de bytecode (ByteBuddy vs mockito-inline). Si un projet utilise déjà Mockito, la migration vers MockK peut être progressive via l'isolation des modules.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi