Писать код, который работает — это полдела. Гораздо важнее убедиться, что он не сломается завтра, через месяц или после добавления новой фичи. Для этого придумали модульные тесты, а чтобы не зависеть от настоящих баз данных, серверов и системных сервисов, используют заглушки — моки. Mockito Kotlin — это библиотека, которая помогает создавать такие заглушки на языке Kotlin, делая код лаконичным и понятным. В этом руководстве я с нуля покажу, как подружить Mockito с Kotlin-проектом, писать моки для классов и проверять, что нужные методы действительно вызывались.
Зачем вообще нужны моки
Представьте, что вы тестируете робот-пылесос. Чтобы проверить, включает ли он двигатель при старте, вам не нужна настоящая квартира с коврами — достаточно убедиться, что на плату двигателя пошёл сигнал. В программировании то же самое: когда вы тестируете ViewModel, которая дёргает сервер через Retrofit, вы не хотите реального интернет-соединения. Вы подменяете Retrofit заглушкой и говорите: «когда вызовут метод getUserName(), верни строку "Иван"». Это и есть мок — объект-пустышка, которым вы управляете. Через mockito kotlin такое поведение настраивается в несколько строк.
Добавление Mockito в проект
Для чистого Kotlin-проекта рекомендую сразу использовать обёртку mockito-kotlin. Она убирает все неудобства, связанные с null и дженериками из Java-версии Mockito, и даёт идиоматичный Kotlin-синтаксис. Вот что нужно добавить в build.gradle.kts:
dependencies {
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("junit:junit:4.13.2")
}
Версии лучше уточнить на Maven Central. Вместе с mockito-kotlin подтянется и сам Mockito, поэтому отдельно его подключать не нужно. Для Android-проектов дополнительно потребуется библиотека mockito-android, если вы используете инструментальные тесты, но для юнит-тестов описанного набора достаточно.
Создание первого мока
Допустим, у нас есть простой репозиторий, который загружает профиль пользователя. Мы хотим протестировать логику, которая показывает приветственное сообщение, не дёргая реальный сервер. Вот как создать мок с помощью mockito kotlin:
interface UserRepository {
fun getUserName(): String
}
val repository = mock<UserRepository>()
Одно слово mock — и у нас в руках объект, который притворяется UserRepository. Теперь можно настроить его поведение. Функция whenever принимает вызов метода и говорит, что вернуть:
whenever(repository.getUserName()).thenReturn("Анна")
Теперь любой вызов repository.getUserName() будет возвращать строку "Анна", будто сервер её прислал.
Проверка вызовов: verify
Создать заглушку мало. Часто нужно убедиться, что определённый метод действительно вызывался, и ровно столько раз, сколько ожидается. За это отвечает verify.
// вызываем метод дважды
repository.getUserName()
repository.getUserName()
// проверяем, что метод был вызван ровно 2 раза
verify(repository, times(2)).getUserName()
Если метод не вызывался или вызывался меньше/больше раз, тест упадёт с понятным сообщением. Можно не указывать times(2) — тогда verify проверит, что метод вызван ровно один раз.
Захват аргументов: ArgumentCaptor
Бывает, что метод принимает параметры, и нужно проверить, с какими именно аргументами он вызывался. Например, вы хотите убедиться, что при сохранении пользователя в базу был передан правильный email. Для этого есть argumentCaptor.
interface MailService {
fun send(to: String, message: String)
}
val mailService = mock<MailService>()
// вызываем метод с аргументом
mailService.send("anna@example.com", "Привет")
// захватываем аргумент
val captor = argumentCaptor<String>()
verify(mailService).send(captor.capture(), any())
// проверяем захваченное значение
assertEquals("anna@example.com", captor.firstValue)
any() означает «любой аргумент этого типа». Он удобен, когда проверять нужно только часть параметров. Захваченные аргументы доступны через captor.firstValue, captor.secondValue или сразу списком через captor.allValues.
Тестируем ViewModel с моками
Теперь применим знания на практике. Типичный сценарий: ViewModel вызывает репозиторий, получает данные и отдаёт их во Flow для UI. Мы протестируем, что при успешном получении данных состояние меняется на Success.
class ProfileViewModel(private val repository: UserRepository) : ViewModel() {
val greeting = MutableStateFlow<String>("")
fun load() {
greeting.value = "Привет, ${repository.getUserName()}!"
}
}
Тест будет выглядеть так:
class ProfileViewModelTest {
private val repository = mock<UserRepository>()
private val viewModel = ProfileViewModel(repository)
@Test
fun `load should update greeting`() {
whenever(repository.getUserName()).thenReturn("Иван")
viewModel.load()
assertEquals("Привет, Иван!", viewModel.greeting.value)
verify(repository).getUserName()
}
}
Тест изолирован: ни сети, ни базы данных. mockito kotlin позволяет написать такой тест за пару минут.
Заглушки для suspend-функций
Корутины — неотъемлемая часть современного Kotlin-кода. Mockito-Kotlin умеет работать с suspend-функциями без дополнительных обёрток.
interface Api {
suspend fun fetchData(): String
}
val api = mock<Api>()
val scope = TestScope()
scope.launch {
whenever(api.fetchData()).thenReturn("ответ")
val result = api.fetchData()
println(result) // ответ
}
Ключевой момент: тест нужно запускать внутри runTest { ... } из библиотеки kotlinx-coroutines-test. Это создаст тестовое окружение, в котором suspend-функции выполняются последовательно и предсказуемо.
Основные аннотации Mockito
В сложных тестовых классах для сокращения кода используют аннотации. Вот основные из арсенала mockito kotlin:
| Аннотация | Что делает | Пример |
|---|---|---|
| @Mock | Создаёт мок-объект | lateinit var repo: UserRepository |
| @InjectMocks | Внедряет моки в тестируемый объект | lateinit var viewModel: ProfileViewModel |
| @MockK (MockK) | Альтернатива от библиотеки MockK | Используется с mockk<T>() |
| @Rule | Правило для JUnit (замена runner) | Подключает MockitoRule |
Для Kotlin аннотации работают так же, как в Java, но чаще предпочитают программный подход (mock(), whenever) — он лаконичнее и лучше читается в цепочках вызовов.
Советы начинающим
- Тестируйте только свой код. Не проверяйте, что Retrofit действительно шлёт HTTP-запросы — мокайте его и проверяйте, что ваш код правильно обрабатывает ответ.
- Один тест — одна проверка. Если тест проверяет и успешный сценарий, и ошибку сети, его сложно читать и он часто ломается. Разделяйте сценарии.
- Избегайте
any()без необходимости. Если можете указать конкретное значение — укажите. Это делает тест строже и защищает от ложных срабатываний. - Пишите тесты одновременно с кодом. Отложенные тесты почти никогда не пишутся. Взялись за новый класс — сразу создайте тестовый класс.
- Не забывайте про
runTest. Без тестового корутин-скоупа suspend-функции в моках будут работать неправильно или падать.
Коротко о главном
Mockito Kotlin — это must-have инструмент для юнит-тестирования на Kotlin. Вы научились создавать моки через mock(), настраивать их поведение через whenever, проверять вызовы через verify и захватывать аргументы через argumentCaptor. С этими знаниями можно протестировать ViewModel, репозиторий или любой другой класс, изолировав его от реальных зависимостей. Начните с небольшого: напишите тест для одного метода вашего текущего проекта — и вы почувствуете, насколько спокойнее становится работа, когда код покрыт тестами.