MockK — је Kotlin-first оквир за креирање мок објеката, дизајниран специјално за екосистем Kotlin узимајући у обзир његове језичке карактеристике: корутине, екстензионе функције, data class и sealed class. За разлику од Mockito-а, који је портован на Kotlin из Java-е, MockK је од почетка пројектован за Kotlin синтаксу и не захтева додатне додатке за рад са final класама. Према подацима MockK.io, библиотека се користи у више од 40% Kotlin пројеката са модуларним тестирањем.
Главно
MockK — је библиотека за креирање мок објеката, написана на Kotlin-у и оптимизована за његову синтаксу. Решава исте задатке као Mockito — изолација тестираног кода од зависности — али то чини користећи Kotlin-специфичне конструкције: ламбде, DSL, reified generics и suspend-функције.
Главна предност MockK-а у односу на портована решења — нативна подршка за Kotlin. У Mockito-у, моковање final класе захтева opt-in (mockito-inline), а статичких метода — mockStatic. MockK ово подржава подразумевано, јер су Kotlin класе подразумевано final, а заобилажење овог ограничења је уграђено у архитектуру библиотеке.
Верзија 1.13.12 (2024) — стабилно издање које подржава Kotlin 2.0, K2 компајлер и мулти-платформске пројекте (KMP). MockK такође ради са Kotlin/Native и Kotlin/JS, што га чини јединим избором за KMP пројекте где ни Mockito ни EasyMock нису применљиви.
MockK је пројектован узимајући у обзир специфику Kotlin-а и користи језичке могућности — reified generics, DSL са ламбдама, inline функције — да би обезбедио концизан и типно-безбедан API без губитка перформанси.
Механизам MockK-а се заснива на бајткод-инструментацији кроз библиотеку ByteBuddy (као и Mockito), али је облачи у Kotlin-пријатељски DSL. Уместо ланаца when().thenReturn(), MockK користи ламбда блокове every { } и coEvery { }, који изгледају као природно проширење језика. Испод хаубе, MockK пресреће позив унутар ламбде, анализира метод и аргументе кроз рефлексију и упарује их са забележеним правилима стаббинга.
Блок every { mock.method() } returns value чита се као „сваки пут када се метод позове, врати вредност”. Оваква декларативна синтакса је ближа Kotlin стилу и елиминише забуну са редоследом аргумената у when(). Захваљујући reified generics у Kotlin-у, тип мока се изводи аутоматски без експлицитног навођења класе.
val repository = mockk<UserRepository>()
// Stubbing: сваки позив findById(1) враћа корисника
every { repository.findById(1) } returns User("Alice")
// Позив и провера
val result = repository.findById(1)
assertEquals("Alice", result.name)
За разлику од Mockito-а, где сваки метод мора бити експлицитно подешен, MockK подржава relaxed mock — мок који враћа „разумне” подразумеване вредности за било који метод: празну листу за List, 0 за Int, празан стринг за String. Ово драстично смањује количину припремног кода.
// Relaxed mock — сви методи враћају подразумеване вредности
val api = mockk<ApiService>(relaxed = true)
// Не захтева стаббинг — вратиће празну листу
println(api.getUsers()) // []
MockK нуди неколико начина за креирање мок објеката: mockk<T>() за строги мок (сваки метод мора бити експлицитно подешен), mockk<T>(relaxed = true) за релаксирани мок и spyk(obj) за креирање шпијуна на реалном објекту.
| Функција | Тип | Понашање без стаббинга |
|---|---|---|
| mockk() | Строги мок | Баца изузетак при позиву не-стабованог метода |
| mockk(relaxed = true) | Релаксирани мок | Враћа подразумевану вредност |
| spyk() | Шпијун | Позива реални метод ако стаб није подешен |
| slot() | Argument Captor | Хвата аргумент за проверу |
Избор између строгог и релаксираног мока зависи од контекста. Строги мок гарантује да тест не користи методе чије понашање није дефинисано — ово повећава поузданост. Релаксирани мок је згодан за брзо прототипирање тестова где нису све зависности важне. У пракси, препоручује се почети са строгим моком и прећи на relaxed само када стаббинг заузима више линија од самог теста.
Every блок — централна конструкција стаббинга у MockK-у. Унутар ламбде се описује позив метода са конкретним аргументима, а затим се враћа вредност кроз returns, баца изузетак кроз throws или израчунава одговор кроз answers.
MockK подржава све сценарије потребне за тестирање: враћање вредности, бацање изузетка, израчунавање одговора на основу аргумената, више одговора по реду (секвенца позива).
// Враћање вредности
every { repo.findById(1) } returns User("Alice")
// Бацање изузетка
every { repo.findById(999) } throws NotFoundException()
// Динамички одговор
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Секвенца одговора
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
Verify у MockK-у је аналоган Mockito.verify() по значењу, али користи Kotlin DSL: verify { mock.method() }. За suspend-функције користи се coVerify { mock.suspendMethod() }, који коректно ради са корутинама и не захтева специјални раннер.
MockK подржава исте модификаторе као Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called). Синтакса је минималистичка — модификатор се прослеђује као први аргумент у verify { }.
// Провера: метод позван тачно 1 пут
verify(exactly = 1) { repo.save(any()) }
// Провера редоследа позива
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify за suspend-функције
coVerify { api.fetchUsers() }
За проверу аргумената користи се slot() — аналог ArgumentCaptor-а. Slot се декларише пре позива, прослеђује у every или verify, и након извршења теста садржи ухваћену вредност.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockK пружа анотације @MockK и @RelaxedMockK за креирање мокова кроз иницијализацију у JUnit 5. Екстензија MockKExtension аутоматски креира мокове пре сваког теста и чисти после — аналогно MockitoExtension-у, али са подршком за relaxed режим.
Анотација @InjectMockKs (или алтернатива @MockK са експлицитним креирањем објекта) убризгава мокове у тестирани инстанцу. Ово смањује boilerplate и чини код теста чистијим.
@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)
}
}
Избор између MockK-а и Mockito-а зависи од састава тима и типа пројекта. Mockito има већи екосистем, више примера и интеграција, али MockK пружа чистију Kotlin синтаксу и нативну подршку за језичке функције. За нове Kotlin пројекте, MockK се препоручује као идиоматичније решење.
| Критеријум | MockK | Mockito |
|---|---|---|
| Синтакса | Kotlin DSL (every, verify) | Java стил (when, thenReturn) |
| Корутине | coEvery, coVerify (нативно) | Захтева додатне библиотеке |
| Final class | Подржано подразумевано | Захтева mockito-inline |
| KMP | Подржано | Није подржано |
| Relaxed mock | Уграђен | Нема аналога |
| Популарност | Растућа у Kotlin заједници | Доминира у Java и хибридним пројектима |
За пројекте на чистом Kotlin-у (без Java класа) MockK је пожељнији: мање boilerplate-а, нативна подршка за корутине, без изненађења са final класама. За хибридне пројекте или тимове са Java позадином, Mockito остаје радна опција — обе библиотеке се могу користити у истом пројекту кроз различите модуле. При миграцији са Mockito-а на MockK, довољно је заменити анотације @Mock са @MockK и преписати блокове when().thenReturn() у формат every { }.
Често постављана питања
Relaxed mock враћа подразумеване вредности за све не-стабоване методе (празна листа, 0, null), без бацања изузетака. Обичан (strict) mock захтева експлицитан стаббинг сваке методе — иначе тест пада. Relaxed mock је згодан за брзе тестове, strict — за поуздане.
MockK подржава моковање екстензионих функција кроз mockkStatic(). Ово је могуће јер су екстензионе функције у Kotlin-у статички методи са првим параметром-примаоцем. За сваку екстензиону функцију потребно је навести класу у којој је декларисана.
Да, MockK подржава Kotlin Multiplatform (KMP) за заједнички код. На платформама JVM, Native и JS може се користити заједнички API mockk(), every, verify. Ово чини MockK јединим избором за KMP пројекте где Mockito не ради.
Користи verifySequence { } — блок у коме се позиви наводе строго у очекиваном редоследу. Ако се стварни редослед разликује, verifySequence ће бацити изузетак са назнаком првог неусаглашеног позива.
Да, технички је могуће, али се не препоручује. Конфликти могу настати на нивоу бајткод-инструментације (ByteBuddy vs mockito-inline). Ако пројекат већ користи Mockito, миграција на MockK може бити постепена кроз изолацију модула.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође