Mock est un objet de substitution qui imite le comportement d'un composant réel et permet de vérifier les interactions avec celui-ci. Contrairement à Stub, qui renvoie simplement une valeur prédéfinie, Mock enregistre le fait de l'appel de méthode, les arguments passés et le nombre d'appels. Selon Mockito (2024), Mock est le type de Test Double le plus populaire dans les projets Java et Kotlin, utilisé dans plus de 70 % des tests unitaires des applications mobiles.
Points clés
Mock est un objet créé par un framework de mocking (Mockito, MockK, EasyMock) qui simule une interface ou une classe et enregistre tous les appels à ses méthodes. Le développeur définit des attentes : la méthode X sera appelée avec les arguments Y et retournera Z. Après l'exécution du test, Mock vérifie que les attentes correspondent aux appels réels.
Le terme provient de la métaphore théâtrale des Test Doubles : un Mock est un « imitateur » qui ne se contente pas de rester sur scène (comme un Dummy) mais joue un rôle et vérifie si l'interaction avec lui était correcte. Si le code testé n'a pas appelé la méthode attendue par Mock, ou l'a appelée avec des arguments incorrects — le test échoue avec un message d'attente violée.
Un Mock est créé via la fabrique du framework : mockk<MyInterface>() ou Mockito.mock(MyClass.java). Le framework génère un objet proxy qui intercepte tous les appels de méthodes. Chaque appel est comparé à des attentes prédéfinies. Si l'appel correspond à une attente — la valeur spécifiée est retournée. Sinon — Mock retourne une valeur par défaut ou lève une exception, selon la configuration.
Mock est essentiel lorsque le code testé interagit avec des composants ayant des effets secondaires : envoi de données au serveur, écriture en base de données, journalisation, analytics, navigation, affichage de dialogues système. Sans Mocks, ces interactions ne peuvent pas être vérifiées sans exécuter une infrastructure réelle. Selon le Google Testing Blog, Mock est le seul moyen de vérifier qu'une application a effectivement envoyé un événement analytics sans lancer un serveur de test.
La différence entre Mock et Stub est l'un des sujets les plus débattus dans les tests. Les deux types remplacent une dépendance réelle, mais de manières fondamentalement différentes.
| Critère | Mock | Stub |
|---|---|---|
| Question principale | La méthode a-t-elle été appelée ? | Quel résultat a été retourné ? |
| Vérification | Comportement (verify) | État (assert) |
| Retour de données | Optionnel | Obligatoire |
| Exemple | verify(analytics).logEvent(“click”) | assertEquals(5, repository.getCount()) |
| Quand utiliser | Effets secondaires | Retour de données |
Un test simple pour décider : demandez-vous « si je supprime cette ligne de code, le test échouera-t-il ? » Si le test vérifie une valeur de retour — vous avez besoin d'un Stub (vérification basée sur assert). Si le test vérifie que le code a appelé une méthode avec les bons arguments — vous avez besoin d'un Mock (vérification basée sur verify). Cette dichotomie suit le modèle Command-Query Separation : les méthodes qui changent l'état (commands) ont besoin de Mocks ; les méthodes qui retournent des données (queries) ont besoin de Stubs.
Choisir entre Mockito et MockK est l'une des premières décisions lors de la configuration de la stack de test pour un projet Android en Kotlin. Les deux bibliothèques remplissent le même objectif, mais avec des approches différentes des fonctionnalités spécifiques à Kotlin.
Mockito est le standard de facto pour les projets Java. La version 5.x prend en charge le mocking pour les classes final, les méthodes statiques et les constructeurs grâce au MockMaker intégré. Pour les projets Kotlin, Mockito nécessite une configuration supplémentaire : les extensions mockito-kotlin pour une syntaxe améliorée, mockito-inline pour les classes final. Mockito ne prend pas en charge les coroutines Kotlin et les fonctions suspend sans adaptateurs supplémentaires.
MockK a été créé spécifiquement pour Kotlin. Il prend nativement en charge les coroutines (coEvery, coVerify), les classes sealed, les classes data, les singletons d'objet et les fonctions d'extension. La syntaxe de MockK utilise un DSL avec des blocs lambda, ce qui semble naturel dans le code Kotlin. MockK prend également en charge le mocking de propriétés sans configuration supplémentaire — ceci est important pour les projets Android utilisant LiveData, StateFlow et Delegates.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
Les benchmarks (JVM Benchmark, 2024) montrent que MockK crée des objets mock 15–20 % plus rapidement que Mockito pour les projets Kotlin grâce au travail direct avec le bytecode Kotlin plutôt qu'avec Java Reflections. Pour les projets avec des milliers de tests unitaires, la différence de vitesse de compilation peut être notable : MockK économise 30–60 secondes sur une exécution complète des tests dans les grands projets.
Examinons trois scénarios : tester un ViewModel avec des dépendances Mock, tester un UseCase avec vérification d'appel API et tester des coroutines avec coVerify.
class ProfileViewModelTest {
private val analytics = mockk<AnalyticsService>()
private val repo = mockk<UserRepository>()
private val vm = ProfileViewModel(repo, analytics)
fun `profile opened logs analytics event`() {
every { analytics.logEvent("profile_opened") } returns Unit
vm.onViewCreated()
verify { analytics.logEvent("profile_opened") }
}
}
class SendMessageUseCaseTest {
private val api = mockk<MessagingApi>()
private val useCase = SendMessageUseCase(api)
fun `send message with correct payload`() = runTest {
val message = Message(text = "Hello", userId = 42)
coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")
val result = useCase.execute(message)
coVerify {
api.sendMessage(match {
it.text == "Hello" && it.userId == 42
})
}
assertTrue(result is MessageResult.Sent)
}
}
class OrderUseCaseTest {
private val api = mockk<OrderApi>()
private val useCase = OrderUseCase(api)
private val slot = slot<OrderRequest>()
fun `order request contains correct items`() = runTest {
coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")
useCase.execute(listOf("item_a", "item_b"))
assertEquals(2, slot.captured.items.size)
assertEquals("item_a", slot.captured.items[0])
}
}
L'utilisation efficace de Mock dans le développement mobile nécessite de la discipline. Enfreindre ces règles transforme les tests en obstacles fragiles qui se brisent à chaque refactorisation.
Une règle stricte : les Mocks ne doivent être créés que pour les dépendances qui franchissent la limite de l'application : clients API, bases de données, système de fichiers, services système (LocationManager, BluetoothAdapter, Camera). Les classes internes de l'application — entités de domaine, Value Objects, utilitaires simples — ne doivent pas être remplacées par des Mocks. Leur comportement est testé via des objets réels.
Chaque test doit contenir exactement une vérification logique — soit verify (pour Mock), soit assert (pour Stub). Ne mélangez pas la vérification d'état et de comportement dans un même test. Si vous devez vérifier à la fois un appel API et son résultat — créez deux tests séparés avec des noms différents. Cette règle, connue sous le nom de « un assert par test », remonte aux recommandations de Kent Beck (2002).
Au-delà du mocking de base, il existe des techniques avancées qui résolvent des tâches spécifiques dans le développement mobile : tester le multithreading, vérifier l'état de Flow et le mocking partiel d'objets réels.
Spy (ou mock partiel) permet de créer un objet qui délègue les appels à l'implémentation réelle mais permet de surcharger des méthodes individuelles. Dans MockK, spyk est créé à partir d'une instance de classe réelle : val repo = spyk(InMemoryUserRepository()). Les appels avec des attentes définies via every passent par le Mock ; le reste passe par l'objet réel. Spy est particulièrement utile pour tester du code legacy où l'injection de dépendances n'a pas encore été implémentée et où vous devez surcharger une seule méthode.
Dans les projets Android modernes utilisant Jetpack Compose, le ViewModel expose l'état via StateFlow. MockK permet de moquer les dépendances Flow, et la bibliothèque Turbine simplifie la vérification des émissions. Le motif classique : MockK pour un UseCase retournant un Flow, Turbine pour vérifier les émissions du ViewModel. Cette stack est recommandée par la documentation Android Testing (Google, 2024) pour les projets utilisant Kotlin Coroutines.
class SearchViewModelTest {
private val searchUseCase = mockk<SearchUseCase>()
private val vm = SearchViewModel(searchUseCase)
fun `search emits results`() = runTest {
coEvery { searchUseCase.search("android") } returns
flowOf(SearchResult.Success(listOf(Item("Android TDD"))))
vm.search("android")
vm.state.test {
val state = awaitItem()
assertTrue(state.items.isNotEmpty())
cancelAndIgnoreRemainingEvents()
}
}
}
Questions fréquentes
Mock est un concept, un type de Test Double qui vérifie le comportement. Mockito est une bibliothèque pour créer des objets Mock en Java et Android. Autres bibliothèques : MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Pour tester les fonctions suspend avec Mock, utilisez MockK (coEvery / coVerify) ou Mockito avec mockito-kotlin. MockK prend en charge les coroutines nativement : coEvery définit le comportement d'une fonction suspend, coVerify vérifie son appel dans une coroutine. Tous les appels suspend doivent être exécutés dans runTest (kotlinx-coroutines-test).
Oui. Dans MockK, utilisez returnsMany : every { api.getData() } returnsMany listOf(response1, response2). Dans Mockito — une chaîne de thenReturn(value1).thenReturn(value2). Ceci est utile pour tester le comportement avec des appels séquentiels retournant différentes réponses.
Dans MockK, utilisez l'annotation @MockK avec relaxed = true et appelez clearMocks(mock) dans la méthode @After. Dans Mockito — Mockito.reset(mock). Meilleure pratique : créer un nouveau Mock pour chaque test via @Before pour éliminer les interférences entre les tests.
MockK fonctionne correctement avec les classes sealed : every { useCase() } returns Result.Success(data). Mockito ne prend pas en charge les classes sealed directement, nécessitant des solutions de contournement. C'est l'une des raisons pour lesquelles MockK est recommandé pour les projets Kotlin plutôt que Mockito.
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