Mock — egy helyettesítő objektum, amely utánozza a valós komponens viselkedését és lehetővé teszi a vele való interakció ellenőrzését. Ellentétben a Stub-bal, amely egyszerűen visszaad egy megadott értéket, a Mock rögzíti a metódus meghívásának tényét, az átadott argumentumokat és a hívások számát. A Mockito (2024) adatai szerint a Mock a legnépszerűbb Test Double típus Java és Kotlin projektekben, amelyet a mobilalkalmazások egységtesztjeinek több mint 70%-ában használnak.
Főbb pontok
Mock — egy objektum, amelyet a mocking keretrendszer (Mockito, MockK, EasyMock) hoz létre, amely egy interfészt vagy osztályt utánoz, és rögzíti az összes metódushívást. A fejlesztő elvárásokat állít be: az X metódus Y argumentumokkal lesz meghívva és Z-t ad vissza. A teszt végrehajtása után a Mock ellenőrzi, hogy az elvárások megegyeznek-e a tényleges hívásokkal.
A kifejezés a Test Doubles színházi metaforájából származik: a Mock egy „utánzó”, amely nem csak áll a színpadon (mint a Dummy), hanem szerepet játszik és ellenőrzi, hogy a vele való interakció helyes volt-e. Ha a tesztelt kód nem hívta meg a metódust, amelyet a Mock elvárt, vagy hibás argumentumokkal hívta meg — a teszt sikertelen lesz a megsértett elvárás üzenetével.
A Mock a keretrendszer gyárán keresztül jön létre: mockk<MyInterface>() vagy Mockito.mock(MyClass.java). A keretrendszer egy proxy-objektumot generál, amely elfogja az összes metódushívást. Minden hívást összehasonlít az előre beállított elvárásokkal (expectations). Ha a hívás megfelel az elvárásnak — a megadott érték kerül visszaadásra. Ha nem — a Mock alapértelmezett értéket ad vissza vagy kivételt dob, a konfigurációtól függően.
Mock kötelező, ha a tesztelt kód olyan komponensekkel lép interakcióba, amelyek mellékhatásokkal rendelkeznek: adatok küldése a szerverre, írás az adatbázisba, naplózás, analitika, navigáció, rendszerpárbeszédablakok megjelenítése. Mock nélkül ezek az interakciók nem ellenőrizhetők a valós infrastruktúra elindítása nélkül. A Google Testing Blog szerint a Mock az egyetlen mód annak ellenőrzésére, hogy az alkalmazás valóban elküldött-e egy analitikai eseményt anélkül, hogy tesztkiszolgálót kellene indítani.
A Mock és a Stub közötti különbség az egyik legtöbbet vitatott téma a tesztelésben. Mindkét típus helyettesíti a valós függőséget, de alapvetően eltérő módon.
| Szempont | Mock | Stub |
|---|---|---|
| Fő kérdés | Meghívták a metódust? | Milyen eredményt adott vissza? |
| Ellenőrzés | Viselkedés (verify) | Állapot (assert) |
| Adatok visszaadása | Opcionális | Kötelező |
| Példa | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| Mikor használjuk | Mellékhatások | Adatok visszaadása |
Egy egyszerű teszt a választáshoz: tegye fel magának a kérdést — „ha törlöm ezt a kódsort, meg fog bukni a teszt?“. Ha a teszt a visszaadott értéket ellenőrzi — Stub szükséges (ellenőrzés assert segítségével). Ha a teszt azt ellenőrzi, hogy a kód a metódust a megfelelő argumentumokkal hívta-e meg — Mock szükséges (ellenőrzés verify segítségével). Ez a dichotómia a Command-Query Separation mintából következik: az állapotot módosító metódusok (commands) Mock-ot igényelnek; az adatokat visszaadó metódusok (queries) Stub-ot igényelnek.
A Mockito és a MockK közötti választás az egyik első döntés egy Android-projekt tesztvermének Kotlinban történő beállításakor. Mindkét könyvtár ugyanazt a feladatot látja el, de eltérő megközelítéssel a Kotlin-specifikus jellemzőkhöz.
Mockito — de facto szabvány a Java projektekhez. Az 5.x verzió támogatja a mock-objektumokat final osztályokhoz, statikus metódusokhoz és konstruktorokhoz a beépített MockMaker-nek köszönhetően. Kotlin projektekhez a Mockito további konfigurációt igényel: mockito-kotlin kiterjesztések a jobb szintaxisért, mockito-inline a final osztályokhoz. A Mockito nem támogatja a Kotlin korutinokat és a suspend függvényeket további adapterek nélkül.
MockK kifejezetten Kotlinhoz készült. Natívan támogatja a korutinokat (coEvery, coVerify), sealed class, data class, object-szingletonokat és extension függvényeket. A MockK szintaxisa DSL-t használ lambda blokkokkal, ami természetesnek tűnik Kotlin kódban. A MockK emellett képes tulajdonságok (property mocking) mockolására további konfiguráció nélkül — ez fontos azon Android projektek számára, amelyek LiveData-t, StateFlow-t és Delegates-t használnak.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
A benchmarkok (JVM Benchmark, 2024) azt mutatják, hogy a MockK 15–20%-kal gyorsabban hoz létre mock-objektumokat, mint a Mockito Kotlin projektekhez, mivel közvetlenül a Kotlin bytecode-dal dolgozik, nem a Java Reflections-szel. Az ezernyi egységteszttel rendelkező projekteknél a build-idő különbsége észrevehető lehet: a MockK 30–60 másodpercet takarít meg a tesztek teljes lefuttatásakor nagy projektekben.
Három forgatókönyvet vizsgálunk meg: ViewModel tesztelése Mock-függőségekkel, UseCase tesztelése API-hívás ellenőrzésével és korutinok tesztelése coVerify-vel.
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])
}
}
A Mock hatékony használata a mobilfejlesztésben fegyelmet igényel. E szabályok megsértése a teszteket törékeny akadályokká változtatja, amelyek minden refaktorálásnál elromlanak.
Szigorú szabály: Mock csak az alkalmazás határát átlépő függőségekhez jön létre: API-kliensek, adatbázisok, fájlrendszer, rendszerszolgáltatások (LocationManager, BluetoothAdapter, Camera). Az alkalmazás belső osztályai — domain entitások, Value Object, egyszerű segédprogramok — nem helyettesíthetők Mock-kal. Viselkedésüket valós objektumokon keresztül teszteljük.
Minden tesztnek pontosan egy logikai ellenőrzést kell tartalmaznia — vagy verify-t (Mock esetén), vagy assert-et (Stub esetén). Ne keverje az állapot és a viselkedés ellenőrzését egyetlen tesztben. Ha mind az API-hívást, mind az eredményt ellenőrizni kell — hozzon létre két külön tesztet eltérő névvel. Ez a szabály, amely „egy assert tesztenként” néven ismert, Kent Beck (2002) ajánlásaiból származik.
Az alapvető mocking mellett léteznek fejlett technikák, amelyek specifikus feladatokat oldanak meg a mobilfejlesztésben: többszálúság tesztelése, Flow állapot ellenőrzése és valós objektumok részleges mockolása.
Spy (vagy részleges mock) lehetővé teszi egy olyan objektum létrehozását, amely a hívásokat a valós implementációhoz delegálja, de lehetővé teszi egyes metódusok felülírását. A MockK-ban a spyk az osztály valós példánya alapján jön létre: val repo = spyk(InMemoryUserRepository()). Azok a hívások, amelyekhez elvárásokat állítottak be every segítségével, a Mock-on keresztül mennek; a többiek — a valós objektumon keresztül. A Spy különösen hasznos olyan örökölt kód teszteléséhez, ahol a függőséginjektálás még nincs bevezetve, és csak egy metódust kell felülírni.
A modern Android projektekben Jetpack Compose-on a ViewModel az állapotot StateFlow segítségével teszi közzé. A MockK lehetővé teszi a Flow-függőségek mockolását, a Turbine könyvtár pedig leegyszerűsíti az emisszió ellenőrzését. A klasszikus minta: MockK a Flow-t visszaadó UseCase-hez, Turbine a ViewModel-emisszió ellenőrzéséhez. Ezt a stacket az Android Testing (Google, 2024) dokumentációja ajánlja Kotlin Coroutines projektekhez.
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()
}
}
}
Gyakran ismételt kérdések
Mock — egy koncepció, a Test Double egy típusa, amely a viselkedést ellenőrzi. Mockito — egy könyvtár Mock-objektumok létrehozásához Java-ban és Android-ban. Egyéb könyvtárak: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
A suspend függvények Mock-kal történő teszteléséhez használja a MockK-t (coEvery / coVerify) vagy a Mockito-t mockito-kotlin-nal. A MockK natívan támogatja a korutinokat: a coEvery meghatározza a suspend függvény viselkedését, a coVerify ellenőrzi annak meghívását a korutinon belül. Az összes suspend hívást a runTest (kotlinx-coroutines-test) segítségével kell végrehajtani.
Igen. A MockK-ban erre a returnsMany szolgál: every { api.getData() } returnsMany listOf(response1, response2). A Mockito-ban — a thenReturn(value1).thenReturn(value2) lánc. Ez hasznos az egymást követő, különböző válaszokkal rendelkező hívások viselkedésének teszteléséhez.
A MockK-ban használja a @MockK annotációt relaxed = true mezővel, és hívja meg a clearMocks(mock)-ot a @After metódusban. A Mockito-ban — Mockito.reset(mock). A legjobb gyakorlat: minden teszthez hozzon létre új Mock-ot @Before segítségével a tesztek közötti befolyás kizárásához.
A MockK megfelelően működik a sealed class-szal: every { useCase() } returns Result.Success(data). A Mockito nem támogatja közvetlenül a sealed class-t, kerülő utakat igényel. Ez az egyik oka annak, hogy Kotlin projektekhez a MockK ajánlott a Mockito helyett.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is