MockK — це Kotlin-first фреймворк для створення mock-об'єктів, розроблений спеціально для екосистеми Kotlin з урахуванням її мовних особливостей: корутин, extension-функцій, data class і sealed class. На відміну від Mockito, який портовано на Kotlin з Java, MockK спочатку проєктувався під Kotlin-синтаксис і не потребує додаткових плагінів для роботи з final-класами. За даними MockK.io, бібліотека використовується більш ніж у 40% Kotlin-проєктів з модульним тестуванням.
Головне
MockK — це бібліотека для створення mock-об'єктів, написана на 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-friendly 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), не кидаючи винятків. Звичайний (строгий) mock потребує явного стабінгу кожного методу — інакше тест падає. Relaxed mock зручний для швидких тестів, strict — для надійних.
MockK підтримує мокування extension-функцій через mockkStatic(). Це можливо тому, що extension-функції в Kotlin — це статичні методи з першим параметром-приймачем. Для кожної extension-функції потрібно вказати клас, у якому вона оголошена.
Так, MockK підтримує Kotlin Multiplatform (KMP) для common-коду. На платформах JVM, Native і JS можна використовувати спільний API mockk(), every, verify. Це робить MockK єдиним вибором для KMP-проєктів, де Mockito не працює.
Використовуйте verifySequence { } — блок, у якому виклики вказуються строго в очікуваному порядку. Якщо реальний порядок відрізняється, verifySequence кине виняток із зазначенням першого неспівпавшого виклику.
Так, технічно це можливо, але не рекомендується. Конфлікти можуть виникнути на рівні байткод-інструментації (ByteBuddy vs mockito-inline). Якщо проєкт уже використовує Mockito, міграція на MockK може бути поступовою через ізоляцію модулів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також