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-specific конструкций: лямбд, 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), не бросая исключений. Обычный (strict) 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также