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 для чего‑то большего, вероятно, делаете что‑то неправильно.»

Возможные решения и рекомендации

  1. Пересмотрите архитектуру. Применяйте принципы SOLID, DDD, чистую архитектуру. Чем меньше скрытых зависимостей, тем реже нужен мок.
  2. Используйте стаб и фейк. Для простых сценариев достаточно написать небольшую реализацию интерфейса.
  3. Выбирайте специализированные библиотеки. Для Kotlin есть MockK — библиотека, учитывающая особенности языка (final‑классы, корутины).
  4. Тестируйте поведение, а не взаимодействие. Фокусируйтесь на результатах работы, а не на том, какие методы были вызваны.
  5. Автоматизируйте проверку покрытий. Инструменты вроде 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‑запроса.


Оригинал
PREVIOUS ARTICLE