MockK ist ein Kotlin-first Framework zum Erstellen von Mock-Objekten, das speziell für das Kotlin-Ökosystem unter Berücksichtigung seiner Sprachmerkmale entwickelt wurde: Koroutinen, Erweiterungsfunktionen, data class und sealed class. Im Gegensatz zu Mockito, das von Java nach Kotlin portiert wurde, wurde MockK von Grund auf für die Kotlin-Syntax entwickelt und benötigt keine zusätzlichen Plugins für die Arbeit mit final-Klassen. Laut MockK.io wird die Bibliothek in mehr als 40% der Kotlin-Projekte mit Komponententests eingesetzt.
Wichtige Punkte
MockK ist eine Bibliothek zum Erstellen von Mock-Objekten, die in Kotlin geschrieben und für dessen Syntax optimiert ist. Sie löst dieselben Probleme wie Mockito — Isolation des getesteten Codes von Abhängigkeiten — aber tut dies mit Kotlin-spezifischen Konstrukten: Lambdas, DSL, reified Generics und suspend-Funktionen.
Der Hauptvorteil von MockK gegenüber portierten Lösungen ist die native Kotlin-Unterstützung. In Mockito erfordert das Mocken einer final-Klasse opt-in (mockito-inline) und statische Methoden benötigen mockStatic. MockK unterstützt dies standardmäßig, da Kotlin-Klassen standardmäßig final sind und die Umgehung dieser Einschränkung in die Architektur der Bibliothek integriert ist.
Version 1.13.12 (2024) ist eine stabile Version, die Kotlin 2.0, den K2-Compiler und plattformübergreifende Projekte (KMP) unterstützt. MockK funktioniert auch mit Kotlin/Native und Kotlin/JS, was es zur einzigen Wahl für KMP-Projekte macht, bei denen weder Mockito noch EasyMock anwendbar sind.
MockK ist unter Berücksichtigung der Besonderheiten von Kotlin konzipiert und nutzt Sprachfunktionen — reified Generics, DSL mit Lambdas, Inline-Funktionen — um eine präzise und typsichere API ohne Leistungseinbußen zu bieten.
Der Mechanismus von MockK basiert auf Bytecode-Instrumentierung durch die Bibliothek ByteBuddy (wie Mockito), verpackt sie jedoch in ein Kotlin-freundliches DSL. Anstelle von when().thenReturn()-Ketten verwendet MockK Lambda-Blöcke every { } und coEvery { }, die wie eine natürliche Erweiterung der Sprache aussehen. Unter der Haube fängt MockK den Aufruf innerhalb des Lambdas ab, analysiert Methode und Argumente durch Reflektion und gleicht sie mit den aufgezeichneten Stubbing-Regeln ab.
Der Block every { mock.method() } returns value wird gelesen als „jedes Mal, wenn die Methode aufgerufen wird, gib den Wert zurück.“ Diese deklarative Syntax ist näher am Kotlin-Stil und beseitigt Verwirrung über die Argumentreihenfolge in when(). Dank Kotlins reified Generics wird der Mock-Typ automatisch abgeleitet, ohne die Klasse explizit angeben zu müssen.
val repository = mockk<UserRepository>()
// Stubbing: Jeder Aufruf von findById(1) gibt den Benutzer zurück
every { repository.findById(1) } returns User("Alice")
// Aufruf und Überprüfung
val result = repository.findById(1)
assertEquals("Alice", result.name)
Im Gegensatz zu Mockito, wo jede Methode explizit konfiguriert werden muss, unterstützt MockK relaxed mock — einen Mock, der für jede Methode „vernünftige“ Standardwerte zurückgibt: eine leere Liste für List, 0 für Int, einen leeren String für String. Dies reduziert die Menge an Setup-Code drastisch.
// Relaxed mock — alle Methoden geben Standardwerte zurück
val api = mockk<ApiService>(relaxed = true)
// Kein Stubbing erforderlich — gibt eine leere Liste zurück
println(api.getUsers()) // []
MockK bietet mehrere Möglichkeiten zum Erstellen von Mock-Objekten: mockk<T>() für strenge Mocks (jede Methode muss explizit konfiguriert werden), mockk<T>(relaxed = true) für relaxierte Mocks und spyk(obj) zum Erstellen eines Spions auf einem realen Objekt.
| Funktion | Typ | Verhalten ohne Stubbing |
|---|---|---|
| mockk() | Strenge Mock | Wirft Ausnahme beim Aufruf einer nicht gestubten Methode |
| mockk(relaxed = true) | Relaxierte Mock | Gibt Standardwert zurück |
| spyk() | Spion | Ruft die echte Methode auf, wenn kein Stub konfiguriert ist |
| slot() | Argument Captor | Erfasst das Argument zur Überprüfung |
Die Wahl zwischen strengem und relaxiertem Mock hängt vom Kontext ab. Ein strenger Mock stellt sicher, dass der Test keine Methoden verwendet, deren Verhalten nicht definiert ist — das erhöht die Zuverlässigkeit. Ein relaxierter Mock ist praktisch für schnelles Test-Prototyping, bei dem nicht alle Abhängigkeiten wichtig sind. In der Praxis wird empfohlen, mit einem strengen Mock zu beginnen und nur dann auf relaxiert umzusteigen, wenn das Stubbing mehr Zeilen beansprucht als der Test selbst.
Der every-Block ist das zentrale Stubbing-Konstrukt in MockK. Innerhalb des Lambdas wird ein Methodenaufruf mit bestimmten Argumenten beschrieben, und dann wird ein Wert über returns zurückgegeben, eine Ausnahme über throws geworfen oder eine Antwort über answers berechnet.
MockK unterstützt alle für Tests erforderlichen Szenarien: Rückgabe eines Werts, Auslösen einer Ausnahme, Berechnen einer Antwort basierend auf Argumenten und mehrere Antworten in Reihenfolge (Aufrufsequenz).
// Wert zurückgeben
every { repo.findById(1) } returns User("Alice")
// Ausnahme werfen
every { repo.findById(999) } throws NotFoundException()
// Dynamische Antwort
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Antwortsequenz
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
Verify in MockK ist im Zweck ähnlich wie Mockito.verify(), verwendet aber Kotlin-DSL: verify { mock.method() }. Für suspend-Funktionen wird coVerify { mock.suspendMethod() } verwendet, das korrekt mit Koroutinen funktioniert und keinen speziellen Runner benötigt.
MockK unterstützt dieselben Modifikatoren wie Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called). Die Syntax ist minimalistisch — der Modifikator wird als erstes Argument in verify { } übergeben.
// Überprüfung: Methode wurde genau 1 Mal aufgerufen
verify(exactly = 1) { repo.save(any()) }
// Aufrufreihenfolge überprüfen
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify für suspend-Funktionen
coVerify { api.fetchUsers() }
Zur Argumentüberprüfung wird slot() verwendet — ein Analogon zu ArgumentCaptor. Ein Slot wird vor dem Aufruf deklariert, an every oder verify übergeben und enthält nach der Testausführung den erfassten Wert.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockK bietet die Annotationen @MockK und @RelaxedMockK zum Erstellen von Mocks durch Initialisierung in JUnit 5. Die Erweiterung MockKExtension erstellt automatisch Mocks vor jedem Test und räumt danach auf — ähnlich wie MockitoExtension, aber mit Unterstützung für den relaxierten Modus.
Die Annotation @InjectMockKs (oder die Alternative @MockK mit expliziter Objekterstellung) injiziert Mocks in die getestete Instanz. Dies reduziert Boilerplate und macht den Testcode sauberer.
@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)
}
}
Die Wahl zwischen MockK und Mockito hängt von der Teamzusammensetzung und dem Projekttyp ab. Mockito hat ein größeres Ökosystem, mehr Beispiele und Integrationen, aber MockK bietet eine sauberere Kotlin-Syntax und native Unterstützung für Sprachfunktionen. Für neue Kotlin-Projekte wird MockK als idiomatischere Lösung empfohlen.
| Kriterium | MockK | Mockito |
|---|---|---|
| Syntax | Kotlin DSL (every, verify) | Java-Stil (when, thenReturn) |
| Koroutinen | coEvery, coVerify (nativ) | Erfordert zusätzliche Bibliotheken |
| Final-Klasse | Standardmäßig unterstützt | Erfordert mockito-inline |
| KMP | Unterstützt | Nicht unterstützt |
| Relaxed mock | Integriert | Kein Äquivalent |
| Beliebtheit | Wachsend in der Kotlin-Community | Dominiert in Java- und Hybrid-Projekten |
Für reine Kotlin-Projekte (ohne Java-Klassen) ist MockK vorzuziehen: weniger Boilerplate, native Koroutinen-Unterstützung, keine Überraschungen mit final-Klassen. Für Hybrid-Projekte oder Teams mit Java-Hintergrund bleibt Mockito eine funktionierende Option — beide Bibliotheken können über verschiedene Module im selben Projekt verwendet werden. Bei der Migration von Mockito zu MockK reicht es aus, die @Mock-Annotationen durch @MockK zu ersetzen und when().thenReturn()-Blöcke in das every { }-Format umzuschreiben.
Häufig gestellte Fragen
Relaxed mock gibt für alle nicht gestubten Methoden Standardwerte (leere Liste, 0, null) zurück, ohne Ausnahmen zu werfen. Ein normaler (strenger) Mock erfordert explizites Stubbing jeder Methode — andernfalls schlägt der Test fehl. Relaxierter Mock ist praktisch für schnelle Tests, strenger für zuverlässige Tests.
MockK unterstützt das Mocken von Erweiterungsfunktionen über mockkStatic(). Dies ist möglich, weil Erweiterungsfunktionen in Kotlin statische Methoden mit dem Empfänger als erstem Parameter sind. Für jede Erweiterungsfunktion müssen Sie die Klasse angeben, in der sie deklariert ist.
Ja, MockK unterstützt Kotlin Multiplatform (KMP) für gemeinsamen Code. Auf JVM-, Native- und JS-Plattformen können Sie die gemeinsame API verwenden: mockk(), every, verify. Dies macht MockK zur einzigen Wahl für KMP-Projekte, bei denen Mockito nicht funktioniert.
Verwenden Sie verifySequence { } — einen Block, in dem Aufrufe streng in der erwarteten Reihenfolge angegeben werden. Wenn die tatsächliche Reihenfolge abweicht, wirft verifySequence eine Ausnahme, die den ersten nicht übereinstimmenden Aufruf angibt.
Ja, technisch ist es möglich, aber nicht empfohlen. Konflikte können auf der Ebene der Bytecode-Instrumentierung auftreten (ByteBuddy vs mockito-inline). Wenn ein Projekt bereits Mockito verwendet, kann die Migration zu MockK schrittweise durch Modulisolierung erfolgen.
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