Android Coden
Android 5 min lesen

Test Doubles: Fakes, Mocks, Stubs und Spies richtig einsetzen

Test Doubles ersetzen echte Abhängigkeiten im Test. Dieser Artikel erklärt Fake, Mock, Stub und Spy und wann du welchen Typ wählst.

Gute Tests laufen schnell, sind deterministisch und hängen von keiner externen Ressource ab – kein Netzwerk, keine echte Datenbank, kein Betriebssystem-Dialog. Das Problem: Reale Android-Apps sind voller Abhängigkeiten. Test Doubles lösen diesen Konflikt, indem sie echte Objekte durch kontrollierbare Ersatzobjekte austauschen. Wer die vier Varianten – Stub, Mock, Fake und Spy – gezielt einsetzen kann, schreibt Tests, die schnell laufen und echte Bugs aufdecken.

Was ist das?

Der Begriff Test Double stammt aus Gerard Meszaros’ Buch „xUnit Test Patterns” und ist der Oberbegriff für jedes Objekt, das im Testcode eine echte Abhängigkeit vertritt – ähnlich einem Stuntman, der den Hauptdarsteller in gefährlichen Szenen ersetzt. Innerhalb dieser Kategorie unterscheidet man vier konkrete Typen:

Stub – Ein Stub gibt vorprogrammierte Werte zurück, prüft aber nicht, wie er aufgerufen wird. Er beantwortet die Frage: „Was soll die Abhängigkeit in diesem Test zurückgeben?” Ein typischer Stub gibt immer dieselbe User-Liste zurück, egal welche Parameter übergeben werden.

Mock – Ein Mock ist ein Stub mit Erwartungen. Du legst vorher fest, welche Methoden mit welchen Argumenten aufgerufen werden sollen, und der Mock schlägt fehl, wenn das nicht passiert. Er prüft das Verhalten deines Codes gegenüber einer Abhängigkeit.

Fake – Ein Fake ist eine echte, vollständig funktionierende Implementierung, die jedoch bewusst vereinfacht ist. Ein Beispiel ist ein In-Memory-Repository, das Daten in einer einfachen MutableList statt in Room speichert. Fakes laufen ohne Nebeneffekte, sind aber testbarer Code – keine Magie.

Spy – Ein Spy umhüllt ein echtes Objekt. Alle Methodenaufrufe werden an die echte Implementierung weitergeleitet, aber gleichzeitig aufgezeichnet. Du kannst danach überprüfen, wie oft und mit welchen Argumenten eine Methode aufgerufen wurde.

Im Android-Alltag sind Stub und Fake die mit Abstand häufigsten Test Doubles. Mocks werden in speziellen Interaktionstests eingesetzt; Spies eher selten.

Wie funktioniert es?

Ein Test Double funktioniert, weil deine Klassen gegen Interfaces oder abstrakte Typen programmiert sind. Ein ViewModel kennt nur ein UserRepository-Interface – welche Implementierung dahinterliegt, ist ihm egal. Im Produktionscode bekommt er das echte Room-Repository per Dependency Injection. Im Test bekommt er einen Fake oder Stub.

Stubs und Mocks mit MockK

In Kotlin-Android-Projekten ist MockK die übliche Wahl für Stubs und Mocks. Die Library ist Kotlin-nativ und unterstützt Coroutines out of the box:

// Stub: getUser gibt immer denselben User zurück
val repo = mockk<UserRepository>()
every { repo.getUser("42") } returns User(id = "42", name = "Max Muster")

// Mock: prüft, ob saveUser exakt einmal aufgerufen wurde
coEvery { repo.saveUser(any()) } just Runs
// ... Code unter Test ausführen ...
coVerify(exactly = 1) { repo.saveUser(any()) }

Fakes handschriftlich schreiben

Für Repositories und Datenquellen sind handgeschriebene Fakes oft überlegen. Sie sind transparent, schnell, und du kannst ihnen Hilfsmethoden für Tests hinzufügen:

class FakeUserRepository : UserRepository {
    private val store = mutableMapOf<String, User>()
    var saveCallCount = 0

    override suspend fun getUser(id: String): User? = store[id]

    override suspend fun saveUser(user: User) {
        store[user.id] = user
        saveCallCount++
    }

    // Test-Hilfsmethode: Startzustand befüllen
    fun seed(vararg users: User) = users.forEach { store[it.id] = it }
}

Das saveCallCount-Feld übernimmt hier eine ähnliche Aufgabe wie ein Mock-Verify, ohne dass du eine externe Library brauchst.

Spies

MockK unterstützt Spies mit spyk:

val realFormatter = DateFormatter()
val spy = spyk(realFormatter)
spy.format(someDate)
verify { spy.format(any()) }

Spies sind nützlich, wenn du eine Klasse nicht vollständig austauschen willst, aber trotzdem Interaktionen aufzeichnen musst.

In der Praxis

Betrachte ein LoginViewModel, das einen AuthRepository und einen Navigator nutzt. Du willst prüfen, ob nach einem erfolgreichen Login die Navigation ausgelöst wird:

@Test
fun `successful login navigates to home`() = runTest {
    // Stub: gibt immer Erfolg zurück
    val authRepo = mockk<AuthRepository>()
    coEvery { authRepo.login(any(), any()) } returns Result.success(Unit)

    // Mock: prüft die Interaktion
    val navigator = mockk<Navigator>(relaxed = true)

    val viewModel = LoginViewModel(authRepo, navigator)
    viewModel.onLoginClicked(email = "[email protected]", password = "secret")

    coVerify { navigator.navigateTo(Screen.Home) }
}

Der AuthRepository ist hier ein Stub – er soll nur einen definierten Wert zurückgeben. Der Navigator ist ein Mock – das Testinteresse gilt seiner Interaktion.

Typische Stolperfalle: Mocks für alles

Der häufigste Fehler ist, jede Abhängigkeit reflexartig mit mockk zu ersetzen. Das führt zu Tests, die mit einer Flut von every { ... } beginnen und schwer zu lesen sind. Noch schlimmer: Wenn sich die interne Implementierung deines Repository ändert, brechen plötzlich Tests, die gar nicht das Repository testen wollten.

Faustregel: Verwende einen Fake, wenn die Abhängigkeit Zustand verwaltet (Datenbank, Cache, Netzwerk). Verwende einen Stub, wenn du nur einen festen Rückgabewert brauchst. Greife zum Mock nur dann, wenn du explizit eine Interaktion – den Aufruf einer bestimmten Methode – verifizieren musst.

Eine zweite Falle: Mocks für Typen, die du nicht besitzt. Die Android-Framework-Klassen wie Context oder Intent mit Mocks zu ersetzen, erzeugt brüchige Tests. Hier sind Fakes oder echte Implementierungen mit dem Robolectric- oder AndroidX-Test-Framework die bessere Wahl.

Fazit

Test Doubles sind keine Schummellösung, sondern ein fundamentales Werkzeug für isolierte, schnelle und verlässliche Tests in Android-Apps. Die Entscheidung zwischen Fake, Stub, Mock und Spy ist keine Geschmacksfrage, sondern folgt dem Testzweck: Brauche ich einen bestimmten Zustand, oder will ich ein bestimmtes Verhalten prüfen? Halte dich an die Faustregel, immer den einfachsten Double-Typ zu wählen, der den Test zum Laufen bringt. Überprüfe deine Entscheidungen aktiv, indem du bestehende Tests im Projekt öffnest, nach mockk, every und Fake-Klassen suchst und dich fragst, ob der jeweilige Typ noch der einfachste passende ist – oder ob ein schlichter Fake die Sache klarer machen würde.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.