Моки: mock, when, thenReturn
PostService зависит от репозитория и уведомителя. В настоящем приложении это база данных и почтовый сервер. Модульному тесту не нужно ни то ни другое: это было бы медленно, ненадёжно, и тест рассылал бы настоящие письма. До сих пор мы подменяли зависимости вручную — репозиторием в памяти и лямбдой. Mockito создаёт такие заглушки автоматически.
Мок (mock) — объект, который реализует интерфейс, но сам ничего не делает. Вы говорите ему, что вернуть на какой вызов (это называется стабинг, stubbing), а потом можете спросить, какие вызовы он получил.
Подключение Mockito
Добавьте в dependencies файла pom.xml:
<!-- pom.xml -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.24.0</version>
<scope>test</scope>
</dependency>mockito-junit-jupiter подтягивает mockito-core и интеграцию с JUnit. На Java 21 и новее при первом тесте Mockito печатает предупреждение:
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: ...
WARNING: A Java agent has been loaded dynamically (...\byte-buddy-agent-1.17.7.jar)Mockito меняет классы в памяти через Java-агент, а будущие JDK запретят подключать агент на лету. Документация Mockito рекомендует передавать агент тестовой JVM явно:
<!-- pom.xml -->
<properties>
<!-- ... -->
<argLine></argLine>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.11.0</version>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xshare:off</argLine>
</configuration>
</plugin>
</plugins>
</build>Цель properties создаёт для каждой зависимости свойство с путём к её jar-файлу, так что ${org.mockito:mockito-core:jar} превращается в путь к Mockito. @{argLine} сохраняет аргументы, которые добавляют другие плагины (это понадобится позже для покрытия), а пустое свойство argLine делает запись корректной, когда таких аргументов нет. -Xshare:off убирает ещё одно предупреждение JVM о совместном использовании классов. После этого предупреждения пропадают.
Первый мок
// src/test/java/com/example/blog/PostServiceMockTest.java
package com.example.blog;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class PostServiceMockTest {
@Test
void findReturnsPostFromRepository() {
PostRepository repository = mock(PostRepository.class);
Notifier notifier = mock(Notifier.class);
when(repository.findById(1L))
.thenReturn(Optional.of(new Post(1L, "Привет, Mockito", "anna", false)));
PostService service = new PostService(repository, notifier);
Post post = service.find(1L);
assertEquals("Привет, Mockito", post.title());
}
}mock(PostRepository.class) создаёт заглушку. when(repository.findById(1L)).thenReturn(...) читается как «когда вызовут findById(1L), верни этот пост». Сервис не знает, что разговаривает с моком: он просто вызывает интерфейс.
Что возвращает мок без настройки
Методы, которым вы не задали ответ, возвращают «пустые» значения:
// src/test/java/com/example/blog/PostServiceMockTest.java
System.out.println(repository.findById(7L));
System.out.println(repository.findAll());
System.out.println(repository.save(new Post(null, "X", "anna", false)));Optional.empty
[]
nullOptional → Optional.empty, коллекции → пустые, числа → 0, boolean → false, остальные объекты → null. Поэтому service.find(7L) на таком моке выбросит PostNotFoundException — это тоже стоит проверить тестом.
Частая ошибка
when() работает только с вызовом на моке. Если по ошибке передать настоящий объект:
PostRepository repository = new InMemoryPostRepository();
when(repository.findById(1L)).thenReturn(...);org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);Создайте объект через mock(...). Та же ошибка возникает, если метод private или static, а также для equals() и hashCode() — их Mockito через when подменить не может.