Какие плюсы и минусы у использования моков (mocks) в модульном тестировании?

«Какие плюсы и минусы у использования моков (mocks) в модульном тестировании?» — вопрос из категории Фреймворки тестирования, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Плюсы моков:

  • Полная изоляция теста: Мок заменяет реальную внешнюю зависимость (базу данных, API, файловую систему), позволяя тестировать только бизнес-логику модуля. Это чистейшая форма unit-теста.
  • Контроль над поведением и состоянием: Можно легко сымитировать любые сценарии — успешное выполнение, исключения, таймауты, возврат специфических данных — что сложно или невозможно воспроизвести с реальной зависимостью.
  • Скорость: Тесты, не выполняющие сетевые запросы или операции ввода-вывода, выполняются на порядки быстрее.
  • Упрощение отладки: Поскольку поведение зависимости предсказуемо и контролируемо, найти причину падения теста проще.
  • Верификация взаимодействий: Моки позволяют проверить, был ли вызван метод зависимости, с какими аргументами и сколько раз. Это полезно для тестирования side effects.

Минусы моков:

  • Риск ложного прохождения: Тест может проходить с моком, но падать в реальной среде из-за несоответствия поведения мока и реальной системы (например, изменился формат ответа API).
  • Сложность поддержки: При изменении интерфейса зависимости (сигнатуры метода) необходимо обновить все моки, что может быть трудоемко.
  • Избыточная специфичность: Тест может стать слишком хрупким, если проверяет не что делает система, а как она это делает (например, точную последовательность внутренних вызовов).
  • Отсутствие проверки интеграции: Моки не тестируют реальное взаимодействие между компонентами. Для этого нужны интеграционные тесты.

Пример использования мока для тестирования сервиса уведомлений (Java/Mockito):

// Класс, который мы тестируем
public class OrderService {
    private NotificationService notificationService;

    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    public void processOrder(Order order) {
        // ... логика обработки заказа ...
        notificationService.sendEmail(order.getUserEmail(), "Your order is processed");
    }
}

// Сам тест
@Test
public void processOrder_ShouldSendNotification() {
    // 1. Создаем мок зависимости
    NotificationService mockNotificationService = Mockito.mock(NotificationService.class);
    OrderService orderService = new OrderService(mockNotificationService);

    Order testOrder = new Order("user@example.com");

    // 2. Выполняем тестируемое действие
    orderService.processOrder(testOrder);

    // 3. Верифицируем, что мок был вызван с ожидаемыми аргументами
    Mockito.verify(mockNotificationService)
           .sendEmail("user@example.com", "Your order is processed");
}