Mock ist ein Ersatzobjekt, das das Verhalten einer realen Komponente nachahmt und die Überprüfung von Interaktionen mit ihr ermöglicht. Im Gegensatz zu Stub, das einfach einen vorgegebenen Wert zurückgibt, zeichnet Mock den Aufruf der Methode, die übergebenen Argumente und die Anzahl der Aufrufe auf. Laut Mockito (2024) ist Mock der beliebteste Test-Double-Typ in Java- und Kotlin-Projekten, der in über 70 % der Modultests mobiler Anwendungen verwendet wird.
Das Wichtigste
Mock ist ein Objekt, das von einem Mocking-Framework (Mockito, MockK, EasyMock) erstellt wird, eine Schnittstelle oder Klasse simuliert und alle Aufrufe seiner Methoden aufzeichnet. Der Entwickler legt Erwartungen fest: Methode X wird mit den Argumenten Y aufgerufen und gibt Z zurück. Nach der Testausführung überprüft Mock, ob die Erwartungen mit den tatsächlichen Aufrufen übereinstimmen.
Der Begriff stammt aus der theatralischen Metapher der Test Doubles: Ein Mock ist ein „Imitator“, der nicht nur auf der Bühne steht (wie ein Dummy), sondern eine Rolle spielt und überprüft, ob die Interaktion mit ihm korrekt war. Wenn der getestete Code die Methode, die Mock erwartete, nicht aufgerufen hat oder mit falschen Argumenten aufgerufen hat — schlägt der Test mit einer Meldung über eine verletzte Erwartung fehl.
Ein Mock wird über die Factory des Frameworks erstellt: mockk<MyInterface>() oder Mockito.mock(MyClass.java). Das Framework generiert ein Proxy-Objekt, das alle Methodenaufrufe abfängt. Jeder Aufruf wird mit vordefinierten Erwartungen verglichen. Wenn der Aufruf einer Erwartung entspricht — wird der angegebene Wert zurückgegeben. Wenn nicht — gibt Mock je nach Konfiguration einen Standardwert zurück oder wirft eine Ausnahme.
Mock ist unverzichtbar, wenn der getestete Code mit Komponenten interagiert, die Nebenwirkungen haben: Senden von Daten an den Server, Schreiben in die Datenbank, Protokollierung, Analytics, Navigation, Anzeigen von Systemdialogen. Ohne Mocks können diese Interaktionen nicht überprüft werden, ohne eine reale Infrastruktur zu betreiben. Laut Google Testing Blog ist Mock die einzige Möglichkeit zu überprüfen, ob eine Anwendung tatsächlich ein Analytics-Ereignis gesendet hat, ohne einen Testserver zu starten.
Der Unterschied zwischen Mock und Stub ist eines der am meisten diskutierten Themen beim Testen. Beide Typen ersetzen eine reale Abhängigkeit, aber auf grundlegend unterschiedliche Weise.
| Kriterium | Mock | Stub |
|---|---|---|
| Hauptfrage | Wurde die Methode aufgerufen? | Welches Ergebnis wurde zurückgegeben? |
| Verifikation | Verhalten (verify) | Zustand (assert) |
| Datenrückgabe | Optional | Erforderlich |
| Beispiel | verify(analytics).logEvent(„click“) | assertEquals(5, repository.getCount()) |
| Wann verwenden | Nebenwirkungen | Datenrückgabe |
Ein einfacher Test zur Entscheidung: Fragen Sie sich „Wenn ich diese Codezeile lösche, wird der Test dann fehlschlagen?“ Wenn der Test einen Rückgabewert prüft — benötigen Sie einen Stub (assert-basierte Verifikation). Wenn der Test prüft, ob der Code eine Methode mit den richtigen Argumenten aufgerufen hat — benötigen Sie einen Mock (verify-basierte Verifikation). Diese Dichotomie folgt dem Command-Query-Separation-Muster: Methoden, die den Zustand ändern (Commands), benötigen Mocks; Methoden, die Daten zurückgeben (Queries), benötigen Stubs.
Die Wahl zwischen Mockito und MockK ist eine der ersten Entscheidungen bei der Einrichtung des Test-Stacks für ein Android-Projekt in Kotlin. Beide Bibliotheken erfüllen den gleichen Zweck, jedoch mit unterschiedlichen Ansätzen für Kotlin-spezifische Funktionen.
Mockito ist der De-facto-Standard für Java-Projekte. Version 5.x unterstützt dank des integrierten MockMaker das Mocken von finalen Klassen, statischen Methoden und Konstruktoren. Für Kotlin-Projekte benötigt Mockito zusätzliche Einrichtung: mockito-kotlin-Erweiterungen für verbesserte Syntax, mockito-inline für finale Klassen. Mockito unterstützt keine Kotlin-Coroutinen und Suspend-Funktionen ohne zusätzliche Adapter.
MockK wurde speziell für Kotlin entwickelt. Es unterstützt nativ Coroutinen (coEvery, coVerify), versiegelte Klassen, Datenklassen, Objekt-Singletons und Erweiterungsfunktionen. Die MockK-Syntax verwendet DSL mit Lambda-Blöcken, was in Kotlin-Code natürlich wirkt. MockK unterstützt auch Property-Mocking ohne zusätzliche Einrichtung — wichtig für Android-Projekte, die LiveData, StateFlow und Delegates verwenden.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
Benchmarks (JVM Benchmark, 2024) zeigen, dass MockK Mock-Objekte 15–20 % schneller erstellt als Mockito für Kotlin-Projekte, dank direkter Arbeit mit Kotlin-Bytecode statt Java Reflections. Bei Projekten mit Tausenden von Modultests kann der Unterschied in der Build-Geschwindigkeit spürbar sein: MockK spart 30–60 Sekunden bei einem vollständigen Testdurchlauf in großen Projekten.
Betrachten wir drei Szenarien: Testen eines ViewModels mit Mock-Abhängigkeiten, Testen eines UseCase mit API-Aufrufüberprüfung und Testen von Coroutinen mit 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])
}
}
Die effektive Nutzung von Mock in der mobilen Entwicklung erfordert Disziplin. Die Verletzung dieser Regeln verwandelt Tests in fragile Hindernisse, die bei jeder Refaktorisierung brechen.
Eine strenge Regel: Mocks sollten nur für Abhängigkeiten erstellt werden, die die Anwendungsgrenze überschreiten: API-Clients, Datenbanken, Dateisystem, Systemdienste (LocationManager, BluetoothAdapter, Camera). Interne Anwendungsklassen — Domain-Entitäten, Value Objects, einfache Hilfsprogramme — sollten nicht durch Mocks ersetzt werden. Ihr Verhalten wird durch reale Objekte getestet.
Jeder Test sollte genau eine logische Prüfung enthalten — entweder verify (für Mock) oder assert (für Stub). Mischen Sie nicht Zustands- und Verhaltensüberprüfung in einem Test. Wenn Sie sowohl einen API-Aufruf als auch sein Ergebnis überprüfen müssen — erstellen Sie zwei separate Tests mit unterschiedlichen Namen. Diese Regel, bekannt als „ein Assert pro Test“, geht auf die Empfehlungen von Kent Beck (2002) zurück.
Über das grundlegende Mocking hinaus gibt es fortgeschrittene Techniken, die spezifische Aufgaben in der mobilen Entwicklung lösen: Testen von Multithreading, Überprüfen des Flow-Zustands und partielles Mocken realer Objekte.
Spy (oder partielles Mock) ermöglicht es, ein Objekt zu erstellen, das Aufrufe an die reale Implementierung delegiert, aber einzelne Methoden überschreiben lässt. In MockK wird spyk basierend auf einer realen Klasseninstanz erstellt: val repo = spyk(InMemoryUserRepository()). Aufrufe mit über every definierten Erwartungen gehen durch den Mock; der Rest durch das reale Objekt. Spy ist besonders nützlich zum Testen von Legacy-Code, bei dem die Abhängigkeitsinjektion noch nicht implementiert ist und Sie nur eine Methode überschreiben müssen.
In modernen Android-Projekten mit Jetpack Compose gibt der ViewModel den Zustand über StateFlow bekannt. MockK ermöglicht das Mocken von Flow-Abhängigkeiten, und die Turbine-Bibliothek vereinfacht die Emissionsüberprüfung. Das klassische Muster: MockK für einen UseCase, der einen Flow zurückgibt, Turbine zur Überprüfung der ViewModel-Emissionen. Dieser Stack wird von der Android-Testing-Dokumentation (Google, 2024) für Projekte mit Kotlin Coroutines empfohlen.
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()
}
}
}
Häufig gestellte Fragen
Mock ist ein Konzept, eine Art Test Double, das Verhalten überprüft. Mockito ist eine Bibliothek zum Erstellen von Mock-Objekten in Java und Android. Andere Bibliotheken: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Zum Testen von Suspend-Funktionen mit Mock verwenden Sie MockK (coEvery / coVerify) oder Mockito mit mockito-kotlin. MockK unterstützt Coroutinen nativ: coEvery definiert das Verhalten einer Suspend-Funktion, coVerify überprüft ihren Aufruf innerhalb einer Coroutine. Alle Suspend-Aufrufe müssen innerhalb von runTest (kotlinx-coroutines-test) ausgeführt werden.
Ja. In MockK verwenden Sie returnsMany: every { api.getData() } returnsMany listOf(response1, response2). In Mockito — eine Kette von thenReturn(value1).thenReturn(value2). Dies ist nützlich zum Testen von Verhalten bei sequenziellen Aufrufen mit unterschiedlichen Antworten.
In MockK verwenden Sie die @MockK-Annotation mit relaxed = true und rufen clearMocks(mock) in der @After-Methode auf. In Mockito — Mockito.reset(mock). Beste Praxis: Erstellen Sie für jeden Test über @Before einen neuen Mock, um Interferenzen zwischen Tests zu vermeiden.
MockK funktioniert korrekt mit versiegelten Klassen: every { useCase() } returns Result.Success(data). Mockito unterstützt versiegelte Klassen nicht direkt und erfordert Workarounds. Dies ist ein Grund, warum MockK für Kotlin-Projekte gegenüber Mockito empfohlen wird.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch