@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. Добавьте его к классу.