Что такое Mock-объект в тестировании?

Ответ

Mock-объект — это специально созданный объект-заглушка, который имитирует поведение реальной зависимости в модульном тесте. Его ключевая особенность — возможность проверки взаимодействий: тест может утверждать, что определенные методы мока были вызваны с ожидаемыми аргументами нужное количество раз.

Зачем это нужно? Чтобы изолировать тестируемый модуль (например, сервис) от внешних систем (база данных, API, файловая система) и сосредоточиться на проверке его бизнес-логики.

Пример на Java с Mockito: Допустим, у нас есть NotificationService, который зависит от EmailSender.

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

    public NotificationService(EmailSender emailSender) {
        this.emailSender = emailSender;
    }

    public void sendWelcomeEmail(String userEmail) {
        String subject = "Welcome!";
        String body = "Thank you for registering.";
        emailSender.sendEmail(userEmail, subject, body); // Взаимодействие с зависимостью
    }
}

// Тест с использованием Mock
import static org.mockito.Mockito.*;

class NotificationServiceTest {
    @Test
    void sendWelcomeEmail_ShouldCallEmailSenderWithCorrectParameters() {
        // 1. Создаем mock-объект
        EmailSender mockEmailSender = mock(EmailSender.class);
        NotificationService service = new NotificationService(mockEmailSender);

        String testEmail = "user@example.com";

        // 2. Выполняем действие
        service.sendWelcomeEmail(testEmail);

        // 3. Верификация (VERIFY): проверяем, что взаимодействие произошло
        // Это главное отличие Mock от Stub. Мы проверяем, был ли вызван метод sendEmail
        // с конкретными аргументами ровно один раз.
        verify(mockEmailSender, times(1))
                .sendEmail(eq(testEmail), eq("Welcome!"), contains("Thank you"));
    }
}

Отличие от других типов тестовых дублеров:

  • Stub: Простая заглушка, которая возвращает предопределенные данные. Не занимается верификацией вызовов.
  • Fake: Упрощенная, но рабочая реализация (например, репозиторий, хранящий данные в памяти HashMap вместо реальной БД). Используется для интеграционного тестирования.
  • Spy: Частичный mock, который оборачивает реальный объект, позволяя отслеживать его вызовы и при необходимости подменять поведение отдельных методов.

Использование моков делает unit-тесты быстрыми, стабильными и сфокусированными исключительно на коде, который мы хотим проверить.

Ответ 18+ 🔞

Давай разберемся, что это за зверь такой — mock-объект. Представь, ты тестируешь какую-нибудь логику, а она, сука, лезет в базу данных, шлёт письма по почте или ещё какую дичь творит. И вот тебе уже терпения ноль ебать, потому что тесты тормозят как вязкая жижа, а ещё они хрупкие — чихни на сеть, и всё, тебе пи**ец.

Вот тут-то и появляется наш спаситель — mock. Это такая хитрая жопа, которая притворяется настоящей зависимостью (той самой базой или почтовиком), но на самом деле это полная липа. Её главный конёк — она умеет следить, как с ней общаются. То есть ты можешь потом проверить: «А вызывался ли метод sendEmail? А с какими аргументами? А сколько раз?». Это и есть верификация взаимодействий, без неё mock — просто манда с ушами.

Зачем это всё? Чтобы изолировать кусок кода, который ты проверяешь, от всего остального гребаного мира. Сосредоточиться на бизнес-логике, а не на том, что база данных сегодня с похмелья.

Смотри пример на Java (с Mockito), сейчас всё станет ясно: Допустим, у нас сервис уведомлений, который орет на почтовик, чтобы тот слал письма.

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

    public NotificationService(EmailSender emailSender) {
        this.emailSender = emailSender;
    }

    public void sendWelcomeEmail(String userEmail) {
        String subject = "Welcome!";
        String body = "Thank you for registering.";
        emailSender.sendEmail(userEmail, subject, body); // Вот тут он дергает зависимость
    }
}

// А вот сам тест, где мы всё и накосячим
import static org.mockito.Mockito.*;

class NotificationServiceTest {
    @Test
    void sendWelcomeEmail_ShouldCallEmailSenderWithCorrectParameters() {
        // 1. Создаём муляж, подделку, фейк — наш mock
        EmailSender mockEmailSender = mock(EmailSender.class);
        NotificationService service = new NotificationService(mockEmailSender);

        String testEmail = "user@example.com";

        // 2. Запускаем тестируемый метод
        service.sendWelcomeEmail(testEmail);

        // 3. А вот и самое важное — ВЕРИФИКАЦИЯ (VERIFY)
        // Мы лезем к нашему mock-у и спрашиваем: «Ну что, чувак, тебя дергали?»
        // Проверяем, что метод sendEmail вызвали ровно один раз с нужными параметрами.
        verify(mockEmailSender, times(1))
                .sendEmail(eq(testEmail), eq("Welcome!"), contains("Thank you"));
    }
}

Чем mock не является, чтобы не было путаницы:

  • Stub (Заглушка): Это просто тупая болванка, которая всегда отвечает «ок» или «ошибка». Она не следит за вызовами, ей да похуй.
  • Fake (Фейк): Это уже почти рабочая, но упрощённая реализация. Типа репозитория в памяти вместо реальной БД. Используется для тестов покрупнее.
  • Spy (Шпион): Это как будто ты подсадил к реальному объекту своего человека. Он и работает, и докладывает тебе, кто к нему обращался. Подозрение ебать чувствую к таким.

Короче, моки — это мощный инструмент, чтобы твои unit-тесты были быстрыми, стабильными и били точно в цель, а не во все дыры давалка.