Encodera

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

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

Оглавление

Шпионы

Мок — пустая заглушка: каждый метод возвращает пустое значение, пока вы не зададите ответ. Шпион (spy) — наоборот: он оборачивает настоящий объект, все методы работают как обычно, но Mockito записывает вызовы и позволяет подменить отдельные методы. Шпионы нужны редко — когда хочется сохранить настоящее поведение и проверить или изменить только небольшую его часть.

Шпион на настоящем репозитории

// src/test/java/com/example/blog/SpyTest.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.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
 
class SpyTest {
 
    @Test
    void spyCallsRealMethods() {
        InMemoryPostRepository repository = spy(new InMemoryPostRepository());
        PostService service = new PostService(repository, mock(Notifier.class));
 
        Post post = service.create("Привет", "anna");
 
        assertEquals(1L, post.id());
        assertEquals(1, repository.findAll().size());
        verify(repository).save(any(Post.class));
    }
}

spy(...) принимает готовый объект. Настоящий save() выдаёт id 1 и сохраняет пост — findAll() действительно возвращает один пост. При этом verify работает так же, как с моком.

Подмена одного метода

// src/test/java/com/example/blog/SpyTest.java
@Test
void spyCanOverrideOneMethod() {
    InMemoryPostRepository repository = spy(new InMemoryPostRepository());
    doReturn(Optional.of(new Post(7L, "Из шпиона", "oleg", false)))
            .when(repository).findById(7L);
    PostService service = new PostService(repository, mock(Notifier.class));
 
    assertEquals("Из шпиона", service.find(7L).title());
}

findById(7L) теперь возвращает подготовленный пост, все остальные методы остаются настоящими.

Мок или шпион

МокШпион
Создаётся изКласса или интерфейсаГотового объекта
Методы без настройкиВозвращают пустые значенияВызывают настоящий код
Настройка ответаwhen(...).thenReturn(...)doReturn(...).when(spy).method(...)
Когда использоватьПочти всегдаСтарый код, проверка вызовов на настоящем объекте

Если хочется сделать шпиона из самого проверяемого класса — например, чтобы подменить один из его собственных методов, — обычно это признак того, что класс делает слишком много. Вынести эту часть в отдельную зависимость и подменить её моком — более чистое решение.

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

Со шпионом when(spy.method()) сначала вызывает настоящий метод — так Java вычисляет аргумент when. Если настоящий метод выбрасывает исключение, до подмены дело не доходит:

PostService service = spy(new PostService(new InMemoryPostRepository(), (to, message) -> { }));
 
when(service.find(7L)).thenReturn(new Post(7L, "Из шпиона", "oleg", false));
[ERROR] com.example.blog.SpyWhenTest.spyWithWhen -- Time elapsed: 1.618 s <<< ERROR!
com.example.blog.PostNotFoundException: Пост не найден: 7
	at com.example.blog.PostService.find(PostService.java:24)
	at com.example.blog.SpyWhenTest.spyWithWhen(SpyWhenTest.java:16)

Настоящий find(7L) заглянул в пустой репозиторий и выбросил исключение. Для шпионов всегда используйте форму doReturn(...).when(spy).method(...): она не вызывает настоящий метод.

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

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