5 шокирующих причин, почему Mockito теряет смысл в Kotlin: революция в тестировании
29 декабря 2025 г.Вступление
Техника моккинга (создание поддельных зависимостей) давно считается обязательным инструментом для юнит‑тестов. Библиотека Mockito стала её символом в мире Java. Но с ростом популярности Kotlin и переходом к более чистой архитектуре многие разработчики задаются вопросом: действительно ли нам нужен Mockito?
Вопрос актуален, потому что от выбора подхода к тестированию зависит скорость разработки, читаемость кода и, в конечном счёте, качество продукта.
Японский хокку, отражающий суть проблемы:
Тени моков тают,
Котлин светит чисто —
Тест без маски.
Пересказ Reddit поста своими словами
В одном из популярных субреддитов пользователь jug6ernaut поделился своим опытом перехода от Java к Kotlin. Он отметил, что разработчики вложили немало усилий, чтобы заставить Mockito работать с Kotlin, однако «особенности Kotlin на JVM сделали развитие Mockito болезненным». По его словам, в проектах, написанных на Kotlin, потребность в Mockito почти исчезла: «Хорошо спроектированное приложение просто не нуждается в моках, а Kotlin упрощает написание таких приложений».
Другие комментаторы поддержали или оспорили эту точку зрения, приводя свои примеры и альтернативные подходы.
Суть проблемы и хакерский подход
- Mockito изначально создавался под Java, где часто приходится «запихивать» зависимости в конструкторы вручную.
- Kotlin предлагает более выразительные средства (расширения, функции‑выражения, data‑классы), которые позволяют писать код без избыточных абстракций.
- Если тестируемый код построен по принципам SOLID и использует интерфейсы, то вместо моков можно просто подставить «ручную» реализацию (стаб, фейк).
Детальный разбор проблемы с разных сторон
Технические ограничения Mockito в Kotlin
Mockito полагается на рефлексию и генерацию прокси‑классов. Kotlin, будучи более строгим к null‑безопасности и использующим final‑классы по умолчанию, часто требует дополнительных аннотаций (@Mockable, open) или специальных плагинов (mockito-inline). Это усложняет настройку и делает тесты менее «чистыми».
Архитектурные преимущества Kotlin
Благодаря поддержке sealed class, data class и функций‑расширений, разработчики могут создавать неизменяемые модели и явно описывать границы контекста. Это снижает количество скрытых зависимостей, а значит, и потребность в моках.
Экономия времени и поддерживаемость
Мок‑библиотеки требуют обучения, поддержки версий и иногда приводят к «флюидным» тестам, где проверяется лишь взаимодействие, а не реальное поведение. При использовании стабов и фейков тесты становятся более предсказуемыми и легче читаемыми.
Практические примеры и кейсы
Рассмотрим два сценария: один с традиционным Mockito, второй — без него, используя простую ручную реализацию.
Сценарий 1: Mockito в Kotlin
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
interface PaymentGateway {
fun process(amount: Int)
}
class OrderService(private val gateway: PaymentGateway) {
fun placeOrder(amount: Int) = gateway.process(amount)
}
// Тест
class OrderServiceTest {
private val gateway = mock<PaymentGateway>()
private val service = OrderService(gateway)
@Test
fun `order triggers payment`() {
service.placeOrder(100)
verify(gateway).process(100) // проверка вызова
}
}
Сценарий 2: Фейк без Mockito
class FakePaymentGateway : PaymentGateway {
var lastAmount: Int? = null
override fun process(amount: Int) {
lastAmount = amount // просто сохраняем значение
}
}
// Тест
class OrderServiceTest {
private val fakeGateway = FakePaymentGateway()
private val service = OrderService(fakeGateway)
@Test
fun `order triggers payment`() {
service.placeOrder(100)
assertEquals(100, fakeGateway.lastAmount) // проверяем состояние
}
}
Во втором примере мы избавились от зависимости от сторонней библиотеки, а тест стал более читаемым и понятным.
Экспертные мнения из комментариев
jug6ernaut: «Я ценю усилия, вложенные в поддержку Kotlin, но честно говоря, Mockito стал для меня всё менее актуален. Хорошо спроектированное приложение просто не нуждается в моках, а Kotlin упрощает их написание.»
texzone: «Как имитировать запросы к API без мок‑фреймворка в Kotlin? Есть ли более подходящие инструменты?»
One_Being7941: «Kotlin strikes again.» (Котлин снова доказывает свою силу.)
LiftingRecipient420: «Догматичные, абсолютные утверждения, как у автора, существуют только в интернете.»
elmuerte: «stubs, fakes, mocks, doubles … они все одно и то же. Если вы программируете против интерфейса, можно просто внедрить специально написанную реализацию. Если вы используете Mockito для чего‑то большего, вероятно, делаете что‑то неправильно.»
Возможные решения и рекомендации
- Пересмотрите архитектуру. Применяйте принципы SOLID, DDD, чистую архитектуру. Чем меньше скрытых зависимостей, тем реже нужен мок.
- Используйте стаб и фейк. Для простых сценариев достаточно написать небольшую реализацию интерфейса.
- Выбирайте специализированные библиотеки. Для Kotlin есть
MockK— библиотека, учитывающая особенности языка (final‑классы, корутины). - Тестируйте поведение, а не взаимодействие. Фокусируйтесь на результатах работы, а не на том, какие методы были вызваны.
- Автоматизируйте проверку покрытий. Инструменты вроде
JaCoCoпокажут, какие части кода действительно нуждаются в тестах.
Заключение и прогноз развития
Mockito остаётся мощным инструментом, но его роль в экосистеме Kotlin постепенно снижается. Ожидается рост популярности MockK и усиление практик, ориентированных на чистый код без избыточных моков. В ближайшие годы мы, вероятно, увидим больше примеров, когда тесты пишутся без сторонних библиотек, а разработчики полагаются на простые стаб‑реализации и контракт‑тестирование.
Практический пример на Python (моделирование API без мок‑библиотеки)
# -*- coding: utf-8 -*-
"""
Пример тестирования сервиса без использования сторонних мок‑библиотек.
Мы создаём «фейковый» HTTP‑клиент, который возвращает предопределённый ответ.
"""
import json
from typing import Any, Dict
# --------------------- Фейковый клиент ---------------------
class FakeHttpClient:
"""Класс‑заглушка, имитирующий HTTP‑запросы."""
def __init__(self, response_data: Dict[str, Any]):
self.response_data = response_data
self.last_url = None
def get(self, url: str) -> str:
"""Запоминаем запрошенный URL и возвращаем JSON‑строку."""
self.last_url = url
return json.dumps(self.response_data)
# --------------------- Сервис, который тестируем ---------------------
class WeatherService:
"""Сервис получает погоду через HTTP‑клиент."""
def __init__(self, http_client):
self.http_client = http_client
def get_temperature(self, city: str) -> float:
"""Запрашивает температуру для города."""
url = f"https://api.example.com/weather?city={city}"
response_json = self.http_client.get(url)
data = json.loads(response_json)
return data["temperature"]
# --------------------- Тест без mock-библиотеки ---------------------
def test_get_temperature():
# Подготавливаем фейковый ответ от API
fake_response = {"temperature": 22.5}
fake_client = FakeHttpClient(fake_response)
# Инжектируем фейковый клиент в сервис
service = WeatherService(fake_client)
# Вызываем метод и проверяем результат
temp = service.get_temperature("Moscow")
assert temp == 22.5, "Температура должна быть 22.5°C"
# Дополнительно проверяем, что запрос был сформирован корректно
expected_url = "https://api.example.com/weather?city=Moscow"
assert fake_client.last_url == expected_url, "URL запроса сформирован неверно"
# Запуск теста при прямом выполнении файла
if __name__ == "__main__":
test_get_temperature()
print("Тест пройден успешно.")
В этом примере мы создали простую заглушку FakeHttpClient, которая имитирует HTTP‑запросы без использования unittest.mock или Mockito. Тест проверяет как бизнес‑логику сервиса, так и корректность формирования URL‑запроса.
Оригинал