JavaRush /Курсы /Модуль 5. Spring /Лекция 133: Mockito для создания моков и заглушек

Лекция 133: Mockito для создания моков и заглушек

Модуль 5. Spring
14 уровень , 2 лекция
Открыта

Когда вы тестируете приложение, часто возникает ситуация, где ваш код зависит от других компонентов, таких как сервисы, репозитории, внешние API или даже сторонние библиотеки. Вместо того чтобы запускать реальный код этих зависимостей в тестах (и тратить на это кучу ресурсов), мы можем воспользоваться моками — "обманками", которые имитируют поведение настоящих объектов. И здесь на арену выходит Mockito.

Представьте себе: вы пишете тест для сервиса, который вызывает базу данных. Реальный вызов к базе мог бы занять слишком много времени, а еще вдруг он приведет к форме SQL-зависимости... ведь бывает и такое. Вместо этого, используя Mockito, мы просто подменим вызов базы специальным объектом, который вернет заранее подготовленный результат. Удобно? Ещё как!


Основные понятия Mockito

Перед тем как перейти к практике, давайте разберемся, что за магия скрывается за терминами моки, заглушки и спаи.

  • Мок (Mock) — это объект, который имитирует поведение реального объекта. Благодаря нему мы можем задать заранее предопределённый ответ на конкретные вызовы.
  • Заглушка (Stub) — похожа на мок, но менее гибкая. Это просто "чистый" объект с заранее прописанным поведением, который используется для тестирования.
  • Спай (Spy) — это реальный объект, но с возможностью следить за тем, как он используется. Дело в том, что иногда вам нужен настоящий функционал объекта, но с возможностью подменить части его поведения.

Начало работы с Mockito

Чтобы воспользоваться Mockito, добавим его зависимость в наш проект.

Подключение Mockito через Maven


<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.0.0</version> <!-- Замените на актуальную версию -->
    <scope>test</scope>
</dependency>

Если вы используете Gradle:


testImplementation 'org.mockito:mockito-core:5.0.0' // Замените на актуальную версию

Также стоит подключить JUnit, если его ещё нет в проекте.


Основные возможности Mockito

1. Создание моков

Создать мок можно с помощью метода mock():


import static org.mockito.Mockito.*;

SomeService someServiceMock = mock(SomeService.class);

Однако, если вы используете JUnit 5, мы рекомендуем использовать аннотацию @Mock для большей читабельности. Для этого потребуется аннотация @ExtendWith(MockitoExtension.class).

Пример:


import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class SomeServiceTest {

    @Mock
    private SomeService someServiceMock;

    // Ваши тесты здесь
}

2. Настройка поведения моков

После создания мока, самое интересное — это задать его поведение. Например, мы хотим, чтобы метод возвращал определённое значение:


when(someServiceMock.someMethod()).thenReturn("Mocked Value");

Пример использования:


@Test
void testMockBehavior() {
    when(someServiceMock.greet("John")).thenReturn("Hello, mocked John!");

    String result = someServiceMock.greet("John");

    assertEquals("Hello, mocked John!", result);
}
Вызов when().thenReturn() позволяет вам буквально "научить" мок, что он должен делать.

3. Проверка вызовов

Mockito позволяет не только задать поведение, но и проверить, вызывался ли конкретный метод — и сколько раз.


@Test
void testMethodInvocation() {
    someServiceMock.greet("John");

    verify(someServiceMock).greet("John"); // Проверка: вызвался ли метод greet с параметром "John"
    verify(someServiceMock, times(1)).greet("John"); // Проверка: вызвался ровно один раз
}
Если метод не вызывался, тест упадёт с ошибкой, вроде: "Wanted but not invoked".

Аннотации Mockito

Для упрощения кода Mockito предоставляет несколько удобных аннотаций:

  1. @Mock: Создает мок для указанного объекта.
    
    @Mock
    private SomeService someServiceMock;
    
  2. @InjectMocks: Автоматически внедряет моки в зависимостях тестируемого объекта.
    
    @InjectMocks
    private SomeController someController;
    

    Пример:

    
    @Test
    void testController() {
        when(someServiceMock.greet("John")).thenReturn("Hello, John!");
    
        String response = someController.greet("John");
    
        assertEquals("Hello, John!", response);
    }
    
  3. @Spy: Создает реальный объект, но позволяет подменять его отдельные методы.
    
    @Spy
    private SomeService realService;
    

Практика: тестирование сервиса

У нас есть сервис GreetingService, который зависит от TimeProvider для формирования приветствия, учитывая текущее время:


public class GreetingService {
    private final TimeProvider timeProvider;

    public GreetingService(TimeProvider timeProvider) {
        this.timeProvider = timeProvider;
    }

    public String getGreeting(String name) {
        int currentHour = timeProvider.getCurrentHour();

        if (currentHour < 12) {
            return "Good morning, " + name + "!";
        } else {
            return "Good afternoon, " + name + "!";
        }
    }
}

В тестах мы замокируем TimeProvider, чтобы проверить работу сервиса:


import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class GreetingServiceTest {

    private final TimeProvider timeProviderMock = mock(TimeProvider.class);
    private final GreetingService greetingService = new GreetingService(timeProviderMock);

    @Test
    void testMorningGreeting() {
        when(timeProviderMock.getCurrentHour()).thenReturn(9); // Утро

        String greeting = greetingService.getGreeting("John");

        assertEquals("Good morning, John!", greeting);
    }

    @Test
    void testAfternoonGreeting() {
        when(timeProviderMock.getCurrentHour()).thenReturn(15); // День

        String greeting = greetingService.getGreeting("John");

        assertEquals("Good afternoon, John!", greeting);
    }
}
Здесь TimeProvider можно считать внешней зависимостью, которую мы замокировали.

Частые ошибки при работе с Mockito

Некоторые ошибки могут неожиданно испортить ваши тесты:

  1. NullPointerException: Забудете инициализировать моки. Решение: используйте @Mock и @ExtendWith(MockitoExtension.class).
  2. Несоответствие поведения и вызова: Если метод вызывается с другими параметрами, чем те, что вы указали в when(). Всегда проверяйте правильность аргументов.
  3. Избыточное поведение моков: "Обучайте" моки только тому, что нужно для данного конкретного теста, иначе ваш тест станет сложным для поддержки.

Mockito — это мощный инструмент, который делает тестирование сложных систем проще и удобнее. Теперь вы знаете, как создавать моки, настраивать их поведение и проверять взаимодействия. В следующих лекциях вы увидите, как все это применяется для тестирования контроллеров и других компонентов Spring-приложений.

Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Артём Уровень 112
14 сентября 2025
Что-то в примере про аннотации Мокито нет никаких аннотаций Мокито.