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

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

Модуль 5. Spring
Рівень 9 , Лекція 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-застосунків.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ