A MockK egy Kotlin-first keretrendszer mock-objektumok létrehozására, amelyet kifejezetten a Kotlin ökoszisztémára terveztek, figyelembe véve annak nyelvi jellemzőit: korutinok, extension-függvények, data class és sealed class. Ellentétben a Mockito-val, amelyet Java-ból portoltak Kotlin-ra, a MockK kezdettől fogva Kotlin szintaxisra épült, és nem igényel további bővítményeket a final osztályokkal való munkához. A MockK.io adatai szerint a könyvtárat a Kotlin-projektek több mint 40%ában használják modulteszteléssel.
Főbb pontok
MockK — egy könyvtár mock-objektumok létrehozására, Kotlin nyelven írva és annak szintaxisára optimalizálva. Ugyanazokat a feladatokat oldja meg, mint a Mockito — a tesztelt kód elkülönítése a függőségektől — de ezt Kotlin-specifikus konstrukciók: lambdák, DSL, reified generics és suspend-függvények segítségével teszi.
A MockK fő előnye a portolt megoldásokkal szemben — a Kotlin natív támogatása. A Mockito-ban a final osztályok mockolása opt-in-t igényel (mockito-inline), a statikus módszereke pedig mockStatic-et. A MockK ezt alapértelmezetten támogatja, mivel a Kotlin osztályok alapértelmezetten finalok, és e korlátozás megkerülése be van építve a könyvtár architektúrájába.
Az 1.13.12 verzió (2024) — egy stabil kiadás, amely támogatja a Kotlin 2.0-t, a K2 fordítót és a többplatformos projekteket (KMP). A MockK működik Kotlin/Native és Kotlin/JS környezetben is, ami egyedüli választássá teszi a KMP projektek számára, ahol sem a Mockito, sem az EasyMock nem alkalmazható.
A MockK a Kotlin sajátosságait figyelembe véve lett tervezve, és kihasználja a nyelv képességeit — reified generics, DSL lambdákkal, inline függvények — hogy tömör és típusbiztos API-t nyújtson teljesítményveszteség nélkül.
A MockK mechanizmusa bájtkód-instrumentáción alapul a ByteBuddy könyvtáron keresztül (mint a Mockito), de Kotlin-barát DSL-be csomagolja azt. A when().thenReturn() láncok helyett a MockK every { } és coEvery { } lambda blokkokat használ, amelyek a nyelv természetes kiterjesztésének tűnnek. A motorháztető alatt a MockK elfogja a lambdán belüli hívást, elemzi a módszert és argumentumokat reflekcióval, és összeveti a rögzített stubbing szabályokkal.
Az every { mock.method() } returns value blokk úgy értendő, hogy „mindenhányszor, amikor a módszert meghívják, adja vissza az értéket”. Az ilyen deklaratív szintaxis közelebb áll a Kotlin stílushoz, és kiküszöböli az argumentumok sorrendjével kapcsolatos zavart a when()-ben. A Kotlin reified generics-ének köszönhetően a mock típusa automatikusan kikövetkeztethető az osztály explicit megadása nélkül.
val repository = mockk<UserRepository>()
// Stubbing: minden findById(1) hívás visszaadja a felhasználót
every { repository.findById(1) } returns User("Alice")
// Hívás és ellenőrzés
val result = repository.findById(1)
assertEquals("Alice", result.name)
Ellentétben a Mockito-val, ahol minden módszert explicit be kell állítani, a MockK támogatja a relaxed mock-ot — olyan mock-ot, amely „ésszerű” alapértelmezett értékeket ad vissza bármely módszerhez: üres listát a List-hez, 0-t az Int-hez, üres sztringet a String-hez. Ez drasztikusan csökkenti az előkészítő kód mennyiségét.
// Relaxed mock — minden módszer alapértelmezett értéket ad vissza
val api = mockk<ApiService>(relaxed = true)
// Nem igényel stubbingot — üres listát ad vissza
println(api.getUsers()) // []
A MockK több módot kínál mock-objektumok létrehozására: mockk<T>() szigorú mock-hoz (minden módszert explicit be kell állítani), mockk<T>(relaxed = true) relaxált mock-hoz és spyk(obj) spy létrehozásához egy valós objektumon.
| Függvény | Típus | Viselkedés stubbing nélkül |
|---|---|---|
| mockk() | Szigorú mock | Kivételt dob nem-stubolt módszer hívásakor |
| mockk(relaxed = true) | Relaxált mock | Alapértelmezett értéket ad vissza |
| spyk() | Spy | Valós módszert hív, ha nincs stub beállítva |
| slot() | Argument Captor | Elfogja az argumentumot ellenőrzéshez |
A választás a szigorú és relaxált mock között a kontextustól függ. A szigorú mock garantálja, hogy a teszt nem használ olyan módszereket, amelyek viselkedése nincs meghatározva — ez növeli a megbízhatóságot. A relaxált mock kényelmes a tesztek gyors prototipizálásához, ahol nem minden függőség számít. A gyakorlatban ajánlott szigorú mock-kal kezdeni, és csak akkor átváltani relaxed-re, ha a stubbing több sort foglal el, mint maga a teszt.
Az every blokk — a stubbing központi konstrukciója a MockK-ban. A lambdán belül egy módszerhívás kerül leírásra konkrét argumentumokkal, majd az érték visszaadása a returns-on, kivétel dobása a throws-on vagy válasz számítása az answers-on keresztül történik.
A MockK támogatja a teszteléshez szükséges összes forgatókönyvet: érték visszaadása, kivétel dobása, válasz számítása argumentumok alapján, több válasz egymás után (hívások sorrendje).
// Érték visszaadása
every { repo.findById(1) } returns User("Alice")
// Kivétel dobása
every { repo.findById(999) } throws NotFoundException()
// Dinamikus válasz
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Válaszok sorrendje
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
A Verify a MockK-ban értelemben hasonlít a Mockito.verify()-hez, de Kotlin DSL-t használ: verify { mock.method() }. A suspend-függvényekhez a coVerify { mock.suspendMethod() } használható, amely helyesen működik a korutinokkal, és nem igényel speciális futtatót.
A MockK ugyanazokat a módosítókat támogatja, mint a Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called). A szintaxis minimalista — a módosító első argumentumként kerül átadásra a verify { } blokkban.
// Ellenőrzés: a módszer pontosan 1-szer lett meghívva
verify(exactly = 1) { repo.save(any()) }
// Hívások sorrendjének ellenőrzése
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify suspend-függvényekhez
coVerify { api.fetchUsers() }
Az argumentumok ellenőrzéséhez a slot() — az ArgumentCaptor megfelelője — használható. A slot a hívás előtt kerül deklarálásra, átadásra az every vagy verify számára, és a teszt végrehajtása után tartalmazza az elfogott értéket.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
A MockK @MockK és @RelaxedMockK annotációkat biztosít mockok létrehozásához inicializálással a JUnit 5-ben. A MockKExtension kiterjesztés automatikusan létrehozza a mockokat minden teszt előtt, és utána tisztít — hasonlóan a MockitoExtension-hez, de a relaxed mód támogatásával.
Az @InjectMockKs annotáció (vagy az alternatív @MockK explicit objektumlétrehozzással) beinjektálja a mockokat a tesztelt példányba. Ez csökkenti a boilerplate-et és tisztábbá teszi a tesztkódot.
@ExtendWith(MockKExtension::class)
class UserServiceTest {
@MockK
lateinit var repository: UserRepository
@InjectMockKs
lateinit var service: UserService
@Test
fun `getUser returns user from repository`() {
every { repository.findById(1) } returns User("Alice")
assertEquals("Alice", service.getUser(1)?.name)
}
}
A választás a MockK és Mockito között a csapat összetételétől és a projekt típusától függ. A Mockito nagyobb ökoszisztémával, több példával és integrációval rendelkezik, de a MockK tisztább Kotlin szintaxist és natív támogatást nyújt a nyelvi funkciókhoz. Új Kotlin projektekhez a MockK ajánlott idiomatikusabb megoldásként.
| Szempont | MockK | Mockito |
|---|---|---|
| Szintaxis | Kotlin DSL (every, verify) | Java stílus (when, thenReturn) |
| Korutinok | coEvery, coVerify (natív) | További könyvtárakat igényel |
| Final class | Alapértelmezetten támogatott | mockito-inline szükséges |
| KMP | Támogatott | Nem támogatott |
| Relaxed mock | Beépített | Nincs megfelelője |
| Népszerűség | Növekvő a Kotlin közösségben | Dominál Java és hibrid projektekben |
Tiszta Kotlin projektekhez (Java osztályok nélkül) a MockK előnyösebb: kevesebb boilerplate, natív korutin támogatás, nincs meglepetés a final osztályokkal. Hibrid projektekhez vagy Java háttérrel rendelkező csapatok számára a Mockito továbbra is működő lehetőség marad — mindkét könyvtár használható egy projektben különböző modulokon keresztül. Mockito-ról MockK-ra való áttéréskor elég a @Mock annotációkat @MockK-ra cserélni és a when().thenReturn() blokkokat every { } formátumra átírni.
Gyakran ismételt kérdések
Relaxed mock alapértelmezett értékeket ad vissza az összes nem-stubolt módszerhez (üres lista, 0, null), kivételek dobása nélkül. A szokásos (strict) mock minden módszer explicit stubbingját igényli — különben a teszt megbukik. A relaxed mock gyors tesztekhez kényelmes, a strict — megbízható tesztekhez.
MockK támogatja az extension-függvények mockolását a mockkStatic() segítségével. Ez azért lehetséges, mert az extension-függvények Kotlin-ban statikus módszerek, amelyek első paramétere a fogadó. Minden extension-függvényhez meg kell adni azt az osztályt, amelyben deklarálva van.
Igen, a MockK támogatja a Kotlin Multiplatform (KMP) közös kódját. A JVM, Native és JS platformokon használható a közös API: mockk(), every, verify. Ez teszi a MockK-ot egyedüli választássá a KMP projektek számára, ahol a Mockito nem működik.
Használd a verifySequence { }-t — egy blokkot, amelyben a hívások szigorúan a várt sorrendben vannak megadva. Ha a tényleges sorrend eltér, a verifySequence kivételt dob az első nem egyező hívás megjelölésével.
Igen, technikailag lehetséges, de nem ajánlott. Konfliktusok léphetnek fel a bájtkód-instrumentáció szintjén (ByteBuddy vs mockito-inline). Ha a projekt már használja a Mockito-t, a MockK-ra való áttérés fokozatosan, modulok elkülönítésével történhet.
Összefoglaló
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