Encodera

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

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

Оглавление

Сопоставители аргументов и ArgumentCaptor

До сих пор мы передавали в when и verify точные значения: findById(1L), send("anna", "..."). Часто точное значение неизвестно или неважно: «на любой id верни этот пост», «в сообщении должен быть заголовок». Для этого в Mockito есть сопоставители аргументов (argument matchers). А когда нужно посмотреть на объект, который сервис создал внутри себя, его перехватывает ArgumentCaptor.

Сопоставители

Сопоставители — статические методы класса org.mockito.ArgumentMatchers:

СопоставительПодходит
any(), any(Post.class)Любой объект, любой Post
anyLong(), anyInt(), anyString()Любое значение типа (для anyString — не null)
eq(value)Ровно это значение
contains("текст"), startsWith("текст")Строка с фрагментом
isNull(), notNull()null / не null
argThat(p -> ...)Ваше собственное условие
// src/test/java/com/example/blog/PostServiceMatchersTest.java
@Test
void anyIdReturnsTheSamePost() {
    when(repository.findById(anyLong()))
            .thenReturn(Optional.of(new Post(5L, "Привет", "anna", false)));
 
    assertEquals("Привет", service.find(1L).title());
    assertEquals("Привет", service.find(42L).title());
}
 
@Test
void notificationMentionsTitle() {
    when(repository.findById(1L))
            .thenReturn(Optional.of(new Post(1L, "Привет", "anna", false)));
 
    service.publish(1L);
 
    verify(notifier).send(eq("anna"), contains("Привет"));
    verify(notifier, never()).send(eq("oleg"), anyString());
}

Первый тест настраивает репозиторий на любой id. Второй проверяет, что уведомление ушло Анне и упоминает заголовок, не повторяя весь текст. Подготовка та же, что в прошлом уроке: repository, notifier и service — поля класса.

Ответ зависит от аргумента: thenAnswer

Настоящий репозиторий возвращает сохранённый пост с новым id. thenAnswer вычисляет результат по аргументам вызова:

// src/test/java/com/example/blog/PostServiceMatchersTest.java
when(repository.save(any(Post.class)))
        .thenAnswer(invocation -> {
            Post post = invocation.getArgument(0);
            return new Post(1L, post.title(), post.author(), post.published());
        });

invocation.getArgument(0) — первый аргумент, переданный в save().

ArgumentCaptor

create() собирает объект Post внутри сервиса. Чтобы проверить его поля, перехватим его на пути в репозиторий:

// src/test/java/com/example/blog/PostServiceMatchersTest.java
@Test
void createSavesTrimmedDraft() {
    when(repository.save(any(Post.class)))
            .thenAnswer(invocation -> {
                Post post = invocation.getArgument(0);
                return new Post(1L, post.title(), post.author(), post.published());
            });
 
    service.create("  Привет  ", "anna");
 
    ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
    verify(repository).save(captor.capture());
    Post saved = captor.getValue();
    assertNull(saved.id());
    assertEquals("Привет", saved.title());
    assertFalse(saved.published());
}

captor.capture() внутри verify работает как сопоставитель, который принимает любой аргумент и запоминает его. getValue() возвращает последнее перехваченное значение, getAllValues() — все, если вызовов было несколько. Тест проверяет, что сервис сохранил черновик без id и с обрезанным заголовком.

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

Если в одном аргументе стоит сопоставитель, сопоставителями должны быть все аргументы этого вызова. Смешивание с обычными значениями:

verify(notifier).send("anna", anyString());
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.example.blog.MatchersMixTest.mixed(MatchersMixTest.java:23)
 
This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.

Оберните обычное значение в eq(): verify(notifier).send(eq("anna"), anyString()).

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

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