Test Doubles — är ersättningsobjekt som används i enhetstester istället för riktiga beroenden. Termen introducerades av Gerard Meszaros i boken “xUnit Test Patterns” (2007) som ett samlingsbegrepp för Mock, Stub, Fake, Spy och Dummy. Enligt Martin Fowler (2024), gör Test Doubles det möjligt att isolera den testade komponenten från dess omgivning, vilket gör testerna deterministiska, snabba och oberoende av externa tjänster.
Huvudpunkter
Test Doubles — är en term från bilindustrin (stuntman, “double” för skådespelare), överförd till mjukvaruutveckling. Precis som en stuntman ersätter en skådespelare i en farlig scen, ersätter Test Double en verklig komponent i ett testscenario. Detta är nödvändigt när det verkliga beroendet inte är tillgängligt, är långsamt, icke-deterministiskt eller har biverkningar.
Konceptet Test Double omfattar fem specifika typer, var och en med sin egen uppgift. Meszaros typologi är kanonisk och används i alla moderna testningsguider. Skillnaden mellan typerna ligger i graden av kontroll och verifiering: från enkel parameterifyllnad (Dummy) till fullständig kontroll av anropssekvensen (Mock).
Huvudsyftet med Test Doubles är isolering av den testade modulen. I mobil utveckling är verkliga beroenden API-servrar, databaser, filsystem, enhetssensorer, systemtjänster (LocationManager, Camera, Bluetooth). Direkt användning av dessa komponenter gör testerna långsamma, bräckliga och beroende av omgivningen. Enligt Google Testing Blog (2023) körs välisolerade enhetstester på millisekunder, medan integrationstester tar sekunder och minuter.
Klassificeringen av Gerard Meszaros omfattar fem typer av Test Doubles, som skiljer sig åt i beteende och användningssyfte. Att förstå skillnaderna mellan dem är grunden för korrekt enhetstestning.
Dummy — är ett objekt som skickas till den testade metoden men aldrig används. Dummy behövs bara för att uppfylla metodens signatur. I Kotlin är detta ofta null, emptyList() eller ett objekt med ersättningar. Dummy bör inte innehålla någon logik — om det anropas bör testet misslyckas.
Fake — är en förenklad men fungerande implementering av ett gränssnitt. Till skillnad från Mock och Stub innehåller Fake verklig affärslogik, men i förenklad form. Det klassiska exemplet — InMemoryUserRepository, som lagrar data i HashMap istället för i en databas. Fake används när logik som beror på tillstånd behöver testas, men utan overhead av verklig infrastruktur.
| Typ | Syfte | Exempel |
|---|---|---|
| Dummy | Fylla parameter | null, tomt objekt |
| Fake | Fungerande förenklad implementering | InMemoryRepository |
| Stub | Returnera fast värde | when(api.getUser()).thenReturn(user) |
| Spy | Registrera anrop för kontroll | verify(spy).save(user) |
| Mock | Kontrollera interaktion | verify(mock).sendEmail(email) |
Stub returnerar förbestämda värden på specifika anrop. Stub kontrollerar inte om det anropats — det bara tillhandahåller data. I Mockito skapas Stub via when(method).thenReturn(value). Stub är idealisk för testning när beroendet måste returnera ett specifikt värde, men själva faktumet att det anropas är inte viktigt.
Spy — är ett omslag runt ett verkligt objekt som registrerar alla anrop för senare verifiering. Till skillnad från Mock, delegerar Spy anrop till det verkliga objektet, men gör det möjligt att kontrollera att de inträffade. I Mockito skapas Spy via spy(realObject). Spy är användbart för partiell mockning när du vill använda ett verkligt objekt men kontrollera vissa anrop.
Mock — är ett objekt med fördefinierade anropsförväntningar. Mock kontrollerar om specifika metoder anropats med specifika argument och i en specifik ordning. Till skillnad från Stub fokuserar Mock på beteendeverifiering, inte på att returnera data. Mock är den kraftfullaste och mest använda typen av Test Double i mobil utveckling.
Skillnaden mellan Mock och Stub orsakar ofta förvirring även bland erfarna utvecklare. Huvudskillnaden ligger i syftet: Stub kontrollerar tillstånd (state verification), Mock kontrollerar beteende (behavior verification).
Stub svarar på frågan: “returnerade koden rätt resultat?”. Mock svarar på frågan: “anropade koden rätt metoder med rätt argument?”. I mobil utveckling används Stub när resultatet är viktigt (t.ex. data från ett repository), och Mock när biverkningar är viktiga (t.ex. skicka e-post, skriva till databas).
// Stub: tillståndskontroll
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: beteendekontroll
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Praktiska exempel på alla fem typer av Test Doubles i Kotlin med användning av MockK — det mest populära mocking-biblioteket för Android-projekt.
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: returnera fast API-svar
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: kontrollera att användaren har sparats
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context används inte inom Logger
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
Valet av Test Double-typ beror på vad som exakt testas: tillstånd, beteende eller integration. I mobil utveckling på Android och iOS har följande rekommendationer bildats.
Vid testning av ViewModel, använd Mock för beroenden som orsakar biverkningar (repositoryn, analys, navigering) och Stub för beroenden som returnerar data (API-klienter, ContentProvider). Detta gör det möjligt att kontrollera att ViewModel korrekt hanterar både lyckade och felaktiga scenarier.
På Repository-nivå föredras Fake (in-memory databasimplementeringar) och Stub (fasta API-svar). Fake möjliggör testning av cache-logik och offline-läge utan konfiguration av SQLite. Stub simulerar olika HTTP-statusar: 200, 404, 500, timeout.
Felaktig användning av Test Doubles — en av de vanligaste orsakerna till bräckliga tester som går sönder vid varje omfaktorering.
Det vanligaste misstaget — att mocka allt. Om varje beroende i ett test är ersatt med Mock, slutar testet att kontrollera verkligt beteende. Mock bör endast vara för externa beroenden (nätverk, databas, filsystem, systemtjänster). Interna komponenter i applikationen (Value Object, data class, enkla verktyg) bör inte ersättas.
Det andra misstaget — att skapa en Mock utan att definiera förväntningar. Om en metod anropas utan every / when, returnerar Mock ett standardvärde (null, 0, false). Detta kan leda till falskt positiva tester när Mock tyst returnerar null och testet tolkar detta som korrekt beteende.
Det tredje misstaget — att kontrollera varje anrop av varje Mock. Verify bör endast användas för anrop som är kritiska ur affärslogikens synvinkel. Överdriven verifiering gör tester bräckliga: ändring av anropsordningen i produktionskod bryter tester utan att ändra beteendet.
Vanliga frågor
Stub returnerar data och kontrollerar tillstånd (vad som returnerades), medan Mock kontrollerar beteende (vilka metoder som anropades). Stub = “returnera X”, Mock = “kontrollera att Y anropades med argument Z”. I verkliga tester fungerar ett objekt ofta både som Stub och Mock samtidigt.
Fake föredras framför Mock när tillståndsberoende logik testas: cachning, offline-läge, transaktioner. Fake (in-memory implementering) möjliggör testning av dessa scenarier utan bräckliga verify-anrop. Mock är mer lämpligt för att kontrollera datasändning: analys, push-notiser, e-post.
För Android-projekt i Kotlin rekommenderas MockK. Det stödjer korutiner, suspend-funktioner, sealed class och extension-funktioner utan extra konfiguration. För Java-projekt är standarden fortfarande Mockito — det mest populära biblioteket med omfattande dokumentation.
För att testa Kotlin Flow, använd biblioteket Turbine tillsammans med MockK. Turbine förenklar kontrollen av Flow-emissioner: du kan kontrollera värdenas ordning, flödets slutförande och undantag. Stub för Flow returnerar flowOf(value), Mock kontrollerar om Flow har samlats in.
Ja, men på nivån av API-svar, inte UI-komponenter. Biblioteken MockWebServer (OkHttp) och WireMock gör det möjligt att ersätta HTTP-svar i UI-tester. Själva UI-komponenterna (Compose, SwiftUI Views) bör inte ersättas — deras beteende testas genom screenshot-tester och Espresso.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också