Mock — är ett ersättningsobjekt som imiterar beteendet hos en verklig komponent och gör det möjligt att verifiera interaktionen med den. Till skillnad från Stub som bara returnerar ett givet värde, registrerar Mock faktumet att en metod anropats, de skickade argumenten och antalet anrop. Enligt data från Mockito (2024) är Mock den mest populära typen av Test Double i Java och Kotlin-projekt, som används i mer än 70% av enhetstester för mobilapplikationer.
Huvudpunkter
Mock — är ett objekt som skapas av mocking-ramverket (Mockito, MockK, EasyMock), som imiterar ett gränssnitt eller en klass och registrerar alla anrop till sina metoder. Utvecklaren ställer in förväntningar: metod X kommer att anropas med argument Y och returnera Z. Efter att testet har körts kontrollerar Mock om förväntningarna överensstämmer med de faktiska anropen.
Termen kommer från teatermetaforen för Test Doubles: Mock är en ”imitator” som inte bara står på scenen (som Dummy), utan spelar en roll och verifierar om interaktionen med den var korrekt. Om den testade koden inte anropade metoden som Mock förväntade sig, eller anropade den med felaktiga argument — misslyckas testet med ett meddelande om en bruten förväntning.
Mock skapas via ramverkets fabrik: mockk<MyInterface>() eller Mockito.mock(MyClass.java). Ramverket genererar ett proxy-objekt som fångar upp alla metodanrop. Varje anrop jämförs med förinställda förväntningar (expectations). Om anropet motsvarar förväntningen — returneras det angivna värdet. Om inte — returnerar Mock ett standardvärde eller kastar ett undantag, beroende på konfigurationen.
Mock är obligatoriskt när den testade koden interagerar med komponenter som har bieffekter: skicka data till servern, skriva till databasen, loggning, analys, navigering, visning av systemdialoger. Utan Mock kan dessa interaktioner inte verifieras utan att starta den verkliga infrastrukturen. Enligt Google Testing Blog är Mock det enda sättet att kontrollera att applikationen verkligen skickade en analysthändelse utan att starta en testserver.
Skillnaden mellan Mock och Stub är ett av de mest diskuterade ämnena inom testning. Båda typerna ersätter det verkliga beroendet, men på fundamentalt olika sätt.
| Kriterium | Mock | Stub |
|---|---|---|
| Huvudfråga | Anropades metoden? | Vilket resultat returnerades? |
| Verifiering | Beteende (verify) | Tillstånd (assert) |
| Dataåtergivning | Valfritt | Obligatoriskt |
| Exempel | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| När använda | Bieffekter | Dataåtergivning |
Ett enkelt test för att välja: fråga dig själv — ”om jag tar bort den här kodraden, kommer testet att misslyckas?”. Om testet verifierar returvärdet — behövs Stub (verifiering via assert). Om testet kontrollerar om koden anropade metoden med rätt argument — behövs Mock (verifiering via verify). Denna dikotomi följer av mönstret Command-Query Separation: metoder som ändrar tillstånd (commands) behöver Mock; metoder som returnerar data (queries) behöver Stub.
Valet mellan Mockito och MockK är ett av de första besluten vid konfigurering av teststacken för ett Android-projekt i Kotlin. Båda biblioteken utför samma uppgift, men med olika syn på Kotlin-specifika funktioner.
Mockito — är de facto-standard för Java-projekt. Version 5.x stöder mock-objekt för final-klasser, statiska metoder och konstruktorer tack vare den inbyggda MockMaker. För Kotlin-projekt kräver Mockito ytterligare konfiguration: mockito-kotlin-tillägg för förbättrad syntax, mockito-inline för final-klasser. Mockito stöder inte Kotlin-korutiner och suspend-funktioner utan extra adaptrar.
MockK skapades specifikt för Kotlin. Det stöder inbyggt korutiner (coEvery, coVerify), sealed class, data class, object-singletoner och extension-funktioner. MockK:s syntax använder DSL med lambda-block, vilket ser naturligt ut i Kotlin-kod. MockK kan också mocka egenskaper (property mocking) utan extra konfiguration — detta är viktigt för Android-projekt som använder LiveData, StateFlow och Delegates.
// 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) visar att MockK skapar mock-objekt 15–20% snabbare än Mockito för Kotlin-projekt tack vare direkt arbete med Kotlin-bytecode, inte Java Reflections. För projekt med tusentals enhetstester kan skillnaden i byggtid vara märkbar: MockK sparar 30–60 sekunder vid fullständig testkörning i stora projekt.
Vi undersöker tre scenarier: testning av ViewModel med Mock-beroenden, testning av UseCase med verifiering av API-anrop och testning av korutiner med 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])
}
}
Effektiv användning av Mock inom mobil utveckling kräver disciplin. Brott mot dessa regler förvandlar tester till bräckliga hinder som går sönder vid varje omfaktorisering.
Strikt regel: Mock skapas endast för beroenden som korsar applikationens gräns: API-klienter, databaser, filsystem, systemtjänster (LocationManager, BluetoothAdapter, Camera). Applikationens interna klasser — domänentiteter, Value Object, enkla verktyg — bör inte ersättas med Mock. Deras beteende testas via verkliga objekt.
Varje test bör innehålla exakt en logisk kontroll — antingen verify (för Mock), eller assert (för Stub). Blanda inte verifiering av tillstånd och beteende i ett test. Om både API-anropet och resultatet måste kontrolleras — skapa två separata tester med olika namn. Denna regel, känd som ”en assert per test”, härstammar från Kent Becks rekommendationer (2002).
Förutom grundläggande mockning finns det avancerade tekniker som löser specifika uppgifter inom mobil utveckling: testning av flertrådning, verifiering av Flow-tillstånd och partiell mockning av verkliga objekt.
Spy (eller partiell mock) gör det möjligt att skapa ett objekt som delegerar anrop till den verkliga implementeringen, men tillåter överskridning av enskilda metoder. I MockK skapas spyk baserat på en verklig instans av klassen: val repo = spyk(InMemoryUserRepository()). Anrop för vilka förväntningar har satts via every går genom Mock; resten — genom det verkliga objektet. Spy är särskilt användbart för testning av äldre kod där beroendeinjektion ännu inte har implementerats, och endast en metod behöver åsidosättas.
I moderna Android-projekt med Jetpack Compose exponerar ViewModel tillstånd via StateFlow. MockK gör det möjligt att mocka Flow-beroenden, och Turbine-biblioteket förenklar verifiering av emission. Det klassiska mönstret: MockK för UseCase som returnerar Flow, Turbine för verifiering av ViewModel-emission. Denna stack rekommenderas av dokumentationen för Android Testing (Google, 2024) för projekt på Kotlin Coroutines.
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()
}
}
}
Vanliga frågor
Mock — är ett koncept, en typ av Test Double som verifierar beteende. Mockito — är ett bibliotek för att skapa Mock-objekt i Java och Android. Andra bibliotek: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
För att testa suspend-funktioner med Mock, använd MockK (coEvery / coVerify) eller Mockito med mockito-kotlin. MockK stöder korutiner inbyggt: coEvery definierar beteendet för suspend-funktionen, coVerify verifierar dess anrop inuti korutinen. Alla suspend-anrop måste utföras inuti runTest (kotlinx-coroutines-test).
Ja. I MockK används returnsMany för detta: every { api.getData() } returnsMany listOf(response1, response2). I Mockito — kedjan thenReturn(value1).thenReturn(value2). Detta är användbart för att testa beteende vid sekventiella anrop med olika svar.
I MockK använd annoteringen @MockK med fältet relaxed = true och anropa clearMocks(mock) i @After-metoden. I Mockito — Mockito.reset(mock). Bästa praxis: skapa en ny Mock för varje test via @Before för att utesluta påverkan mellan tester.
MockK fungerar korrekt med sealed class: every { useCase() } returns Result.Success(data). Mockito stöder inte sealed class direkt, utan kräver kringgångar. Detta är en av anledningarna till varför MockK rekommenderas för Kotlin-projekt istället för Mockito.
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å