Mock — mi ez, mock-objektumok és könyvtárak tesztekhez

Szerző: IT Sectr Megjelenés: 2026-04-10 Olvasási idő: 8 perc

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 — objektum, amely ellenőrzi az interakciókat: mely metódusok lettek meghívva, milyen argumentumokkal és hányszor
  • Mockito — a legnépszerűbb könyvtár Mock létrehozásához Java és Android projektekben
  • MockK — alternatíva a Mockito-hoz Kotlinhoz natív korutin és sealed class támogatással
  • Behavior verification — a Mock és Stub közötti kulcsfontosságú különbség: a Mock a viselkedést ellenőrzi, nem az állapotot
  • Over-mocking — a fő Anti-Pattern: a mock csak külső függőségekhez használható

Mi az a Mock?

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.

Hogyan működik a Mock

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.

Mikor szükséges a Mock

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.

Mock és Stub: részletes összehasonlítás

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.

SzempontMockStub
Fő kérdésMeghívták a metódust?Milyen eredményt adott vissza?
EllenőrzésViselkedés (verify)Állapot (assert)
Adatok visszaadásaOpcionálisKötelező
Példaverify(analytics).logEvent("click")assertEquals(5, repository.getCount())
Mikor használjukMellékhatásokAdatok visszaadása

Gyakorlati szabály: Mock vagy nem

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.

Mockito és MockK: a könyvtárak összehasonlítása

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: bevált klasszikus

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: Kotlin-first megközelítés

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.

kotlin
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)

// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user

Teljesítmény összehasonlítás

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.

Mock tesztek példái Kotlinban

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.

1. példa: ViewModel mockolt analitikával

kotlin
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") }
    }
}

2. példa: UseCase aszinkron ellenőrzéssel

kotlin
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)
    }
}

3. példa: argumentumok ellenőrzése ArgumentCaptor segítségével

kotlin
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 tesztelés legjobb gyakorlatai

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.

Csak az alkalmazás külső határait mockolja

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.

Egy assert/verify tesztenként

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.

  • Használja a relaxUnitFun = true-t a MockK-ban az Unit-ot visszaadó metódusokhoz — különben a Mock kivételt dob egy nem leírt hívásra
  • Korlátozza a verify-t csak kritikus hívásokra — ne ellenőrizzen minden gettert és settert, ez törékennyé teszi a teszteket
  • Alkalmazza az ArgumentMatchers-t értelmesen — az any() elrejti a fontos részleteket, ha az argumentum kritikus az üzleti logika szempontjából
  • Ne éljen vissza a verifyNoMoreInteractions-szel — ez a metódus szükségtelenül merevvé teszi a tesztet a termelési kód bármely változásával szemben
  • Használja a @MockkAnnotations-t a Mock-objektumok automatikus inicializálásához — ez csökkenti a boilerplate-ot és javítja az olvashatóságot

A Mock tesztelés fejlett technikái

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.

Részleges Mock spyK-val

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.

StateFlow tesztelése Turbine-nal

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.

kotlin
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

Miben különbözik a Mock a Mockito-tól?

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).

Hogyan működik a Mock Kotlin-korutinokkal?

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.

Visszaadhat a Mock különböző értékeket ismételt hívásoknál?

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.

Hogyan lehet törölni a Mock állapotát a tesztek között?

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.

Hogyan kezeli a Mock a sealed class-t Kotlinban?

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

  • Mock — a Test Double azon típusa, amely a függőségek viselkedését (verify) ellenőrzi, nem az állapotát (assert)
  • Mockito — szabvány Java/Android-hoz, MockK — Kotlin-first választás korutin és sealed class támogatással
  • Fő szabály: Mock a külső határokhoz (hálózat, DB, rendszerszolgáltatások), valós objektumok a belső osztályokhoz
  • Over-mocking — a fő Anti-Pattern: a függőségek túlzott helyettesítése törékennyé és kevéssé hasznossá teszi a teszteket
  • Egy teszt — egy logikai ellenőrzés: verify Mock esetén vagy assert Stub esetén, de nem mindkettő egy tesztben
  • ArgumentCaptor / slot — a Mock-hívás argumentumai ellenőrzésének helyes módja a vak any() helyett
  • A MockK ajánlott Kotlin projektekhez: a coEvery és a coVerify natívan, további adapterek nélkül működik a korutinokkal

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.

Projekt megbeszélése

Olvassa el is