Test Doubles — zijn vervangingsobjecten die in eenheidstests worden gebruikt in plaats van echte afhankelijkheden. De term is geïntroduceerd door Gerard Meszaros in het boek „xUnit Test Patterns“ (2007) als een overkoepelend begrip voor Mock, Stub, Fake, Spy en Dummy. Volgens Martin Fowler (2024), stellen Test Doubles in staat om het geteste component te isoleren van zijn omgeving, waardoor tests deterministisch, snel en onafhankelijk van externe services worden.
Belangrijkste
Test Doubles — is een term uit de auto-industrie (stuntman, „double“ voor acteurs), overgebracht naar softwareontwikkeling. Zoals een stuntman een acteur vervangt in een gevaarlijke scène, vervangt Test Double een echt component in een testscenario. Dit is nodig wanneer de echte afhankelijkheid niet beschikbaar, traag, niet-deterministisch of met bijwerkingen is.
Het concept Test Double omvat vijf specifieke soorten, elk met een eigen taak. De typologie van Meszaros is canoniek en wordt gebruikt in alle moderne testhandleidingen. Het verschil tussen de typen zit in de mate van controle en verificatie: van eenvoudige parameterinvulling (Dummy) tot volledige controle van de aanroepvolgorde (Mock).
Het hoofddoel van Test Doubles is isolatie van de geteste module. In mobiele ontwikkeling zijn echte afhankelijkheden API-servers, databases, bestandssysteem, apparaatsensoren, systeemdiensten (LocationManager, Camera, Bluetooth). Direct gebruik van deze componenten maakt tests traag, kwetsbaar en afhankelijk van de omgeving. Volgens Google Testing Blog (2023) worden goed geïsoleerde eenheidstests uitgevoerd in milliseconden, en integratietests in seconden en minuten.
De classificatie van Gerard Meszaros omvat vijf soorten Test Doubles, die verschillen in gedrag en gebruiksdoel. Het begrijpen van het verschil tussen hen is de basis van correct eenheidstesten.
Dummy — is een object dat wordt doorgegeven aan de geteste methode, maar nooit wordt gebruikt. Dummy is alleen nodig om aan de methodehandtekening te voldoen. In Kotlin is dit vaak null, emptyList() of een object met vervangingen. Dummy mag geen logica bevatten — als het wordt aangeroepen, moet de test falen.
Fake — is een vereenvoudigde maar werkende implementatie van een interface. In tegenstelling tot Mock en Stub bevat Fake echte bedrijfslogica, maar in vereenvoudigde vorm. Het klassieke voorbeeld — InMemoryUserRepository, dat gegevens opslaat in HashMap in plaats van een database. Fake wordt gebruikt wanneer logica die afhankelijk is van toestand moet worden getest, maar zonder de overhead van echte infrastructuur.
| Type | Doel | Voorbeeld |
|---|---|---|
| Dummy | Parameter invullen | null, leeg object |
| Fake | Werkende vereenvoudigde implementatie | InMemoryRepository |
| Stub | Vaste waarde retourneren | when(api.getUser()).thenReturn(user) |
| Spy | Aanroepen registreren voor controle | verify(spy).save(user) |
| Mock | Interactie controleren | verify(mock).sendEmail(email) |
Stub retourneert vooraf bepaalde waarden op specifieke aanroepen. Stub controleert niet of het is aangeroepen — het levert gewoon gegevens. In Mockito wordt Stub gemaakt via when(method).thenReturn(value). Stub is ideaal voor testen wanneer de afhankelijkheid een specifieke waarde moet retourneren, maar het feit van de aanroep zelf niet belangrijk is.
Spy — is een wrapper rond een echt object dat alle aanroepen registreert voor latere verificatie. In tegenstelling tot Mock, delegeert Spy aanroepen naar het echte object, maar maakt het mogelijk te controleren dat ze hebben plaatsgevonden. In Mockito wordt Spy gemaakt via spy(realObject). Spy is nuttig voor gedeeltelijk mocking wanneer u een echt object wilt gebruiken maar sommige aanroepen wilt controleren.
Mock — is een object met vooraf gedefinieerde aanroepverwachtingen. Mock controleert of bepaalde methoden zijn aangeroepen met bepaalde argumenten en in een bepaalde volgorde. In tegenstelling tot Stub, richt Mock zich op gedragsverificatie, niet op het retourneren van gegevens. Mock is het krachtigste en meest gebruikte type Test Double in mobiele ontwikkeling.
Het verschil tussen Mock en Stub veroorzaakt vaak verwarring, zelfs bij ervaren ontwikkelaars. Het belangrijkste verschil zit in het doel: Stub controleert toestand (state verification), Mock controleert gedrag (behavior verification).
Stub beantwoordt de vraag: „heeft de code het juiste resultaat geretourneerd?“. Mock beantwoordt de vraag: „heeft de code de juiste methoden met de juiste argumenten aangeroepen?“. In mobiele ontwikkeling wordt Stub gebruikt wanneer het resultaat belangrijk is (bijv. gegevens uit de repository), en Mock wanneer bijwerkingen belangrijk zijn (bijv. e-mail verzenden, schrijven naar database).
// Stub: toestand controleren
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: gedrag controleren
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Praktijkvoorbeelden van alle vijf soorten Test Doubles in Kotlin met behulp van MockK — de meest populaire mocking-bibliotheek voor Android-projecten.
class InMemoryUserRepository : UserRepository {
private val store = mutableMapOf<String, User>()
override fun save(user: User) {
store[user.email] = user
}
override fun findByEmail(email: String): User? {
return store[email]
}
}
class RegisterUseCaseTest {
private val api = mockk<AuthApi>()
private val repo = spyk(InMemoryUserRepository())
private val useCase = RegisterUseCase(api, repo)
fun `register user successfully`() = runTest {
// Stub: vast API-antwoord retourneren
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: controleren dat gebruiker is opgeslagen
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context wordt niet gebruikt binnen Logger
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
De keuze van het Test Double-type hangt af van wat precies wordt getest: toestand, gedrag of integratie. In mobiele ontwikkeling op Android en iOS zijn de volgende aanbevelingen ontstaan.
Bij het testen van ViewModel gebruikt u Mock voor afhankelijkheden die bijwerkingen veroorzaken (repositories, analytics, navigatie) en Stub voor afhankelijkheden die gegevens retourneren (API-cliënten, ContentProvider). Dit maakt het mogelijk te controleren of ViewModel zowel succesvolle als foutscenario's correct afhandelt.
Op Repository-niveau hebben Fake (in-memory database-implementaties) en Stub (vaste API-antwoorden) de voorkeur. Fake maakt het mogelijk cache-logica en offline modus te testen zonder SQLite-configuratie. Stub simuleert verschillende HTTP-statussen: 200, 404, 500, timeout.
Onjuist gebruik van Test Doubles — een van de meest voorkomende oorzaken van kwetsbare tests die bij elke refactoring breken.
De meest voorkomende fout — alles mocken. Als elke afhankelijkheid in een test is vervangen door Mock, stopt de test met het controleren van echt gedrag. Mock zou alleen voor externe afhankelijkheden moeten zijn (netwerk, database, bestandssysteem, systeemdiensten). Interne componenten van de applicatie (Value Object, data class, eenvoudige hulpprogramma's) mogen niet worden vervangen.
De tweede fout — het maken van een Mock zonder verwachtingen te definiëren. Als een methode wordt aangeroepen zonder every / when, retourneert Mock een standaardwaarde (null, 0, false). Dit kan leiden tot vals-positieve tests waarbij Mock stilzwijgend null retourneert en de test dit interpreteert als correct gedrag.
De derde fout — elke aanroep van elke Mock controleren. Verify mag alleen worden gebruikt voor aanroepen die kritisch zijn vanuit het oogpunt van bedrijfslogica. Overmatige verificatie maakt tests kwetsbaar: verandering van de aanroepvolgorde in productiecode breekt tests zonder gedragsverandering.
Veelgestelde vragen
Stub retourneert gegevens en controleert de toestand (wat is geretourneerd), en Mock controleert het gedrag (welke methoden zijn aangeroepen). Stub = „geef X terug“, Mock = „controleer dat Y is aangeroepen met argument Z“. In echte tests fungeert een object vaak zowel als Stub als Mock tegelijkertijd.
Fake heeft de voorkeur boven Mock wanneer toestandsafhankelijke logica wordt getest: caching, offline modus, transacties. Fake (in-memory implementatie) maakt het mogelijk deze scenario's te testen zonder kwetsbare verify-aanroepen. Mock is geschikter voor het controleren van gegevensverzending: analytics, pushmeldingen, e-mail.
Voor Android-projecten in Kotlin wordt MockK aanbevolen. Het ondersteunt coroutines, suspend-functies, sealed classes en extension-functies zonder extra configuratie. Voor Java-projecten blijft Mockito de standaard — de populairste bibliotheek met uitgebreide documentatie.
Voor het testen van Kotlin Flow gebruikt u de bibliotheek Turbine in combinatie met MockK. Turbine vereenvoudigt het controleren van Flow-emissies: u kunt de volgorde van waarden, het beëindigen van de stroom en uitzonderingen controleren. Stub voor Flow retourneert flowOf(value), Mock controleert of Flow is verzameld.
Ja, maar op het niveau van API-antwoorden, niet UI-componenten. De bibliotheken MockWebServer (OkHttp) en WireMock maken het mogelijk HTTP-antwoorden in UI-tests te vervangen. UI-componenten zelf (Compose, SwiftUI Views) mogen niet worden vervangen — hun gedrag wordt getest via screenshot-tests en Espresso.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook