Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

@Mock и @InjectMocks с MockitoExtension

Создавать моки через mock(...) и передавать их в конструктор вручную нормально, пока зависимостей две. Когда их больше, подготовка разрастается. Mockito может сделать это сам: поля с @Mock становятся моками, а поле с @InjectMocks получает объект проверяемого класса, в который переданы эти моки. Расширение JUnit MockitoExtension связывает это с жизненным циклом теста.

Аннотации

// src/test/java/com/example/blog/PostServiceAnnotationsTest.java
package com.example.blog;
 
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
 
import java.util.Optional;
 
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
@ExtendWith(MockitoExtension.class)
class PostServiceAnnotationsTest {
 
    @Mock
    PostRepository repository;
 
    @Mock
    Notifier notifier;
 
    @InjectMocks
    PostService service;
 
    @Test
    void publish() {
        when(repository.findById(1L))
                .thenReturn(Optional.of(new Post(1L, "Привет", "anna", false)));
 
        Post post = service.publish(1L);
 
        assertTrue(post.published());
        verify(notifier).send("anna", "Ваш пост «Привет» опубликован");
    }
}
ЧастьЧто делает
@ExtendWith(MockitoExtension.class)Перед каждым тестом создаёт моки и заполняет поля
@MockПоле становится новым моком своего типа
@InjectMocksСоздаёт PostService и передаёт моки в конструктор по типу
@CaptorПоле становится ArgumentCaptor: @Captor ArgumentCaptor<Post> captor;

@InjectMocks выбирает конструктор с наибольшим числом параметров. Если подходящего конструктора нет, пробует сеттеры и поля. Внедрение через конструктор надёжнее всего: сервис не может существовать без своих зависимостей.

Строгие заглушки

MockitoExtension строгий: он роняет тест, который настраивает ответ, не используемый кодом. Такой when обычно остался после копирования или говорит о том, что тест проверяет не то, что думает:

// src/test/java/com/example/blog/PostServiceAnnotationsTest.java
@Test
void publishedIsEmpty() {
    when(repository.findById(1L))
            .thenReturn(Optional.of(new Post(1L, "Привет", "anna", false)));
    when(repository.findAll()).thenReturn(List.of());
 
    assertEquals(0, service.published().size());
}
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.example.blog.UnnecessaryTest.publishedIsEmpty(UnnecessaryTest.java:29)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.

published() вызывает только findAll(), поэтому заглушка findById лишняя — удалите её. В редких случаях, когда заглушка общая для нескольких тестов и нужна не каждому, пометьте её lenient().when(...).

Частая ошибка

Аннотации сами по себе ничего не делают: без @ExtendWith(MockitoExtension.class) моки никто не создаёт, и поля остаются null:

[ERROR] com.example.blog.NoExtensionTest.publish -- Time elapsed: 0.050 s <<< ERROR!
java.lang.NullPointerException: Cannot invoke "com.example.blog.PostRepository.findById(long)" because "this.repository" is null
	at com.example.blog.NoExtensionTest.publish(NoExtensionTest.java:24)

NullPointerException на поле с @Mock почти всегда означает забытый @ExtendWith. Добавьте его к классу.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.