Argument matchers and ArgumentCaptor
So far we passed exact values to when and verify: findById(1L), send("anna", "..."). Often the exact value is unknown or unimportant: "for any id return this post", "the message must mention the title". For this Mockito has argument matchers. And when you need to look at an object the service created inside, an ArgumentCaptor catches it.
Matchers
Matchers are static methods of org.mockito.ArgumentMatchers:
| Matcher | Matches |
|---|---|
any(), any(Post.class) | Any object, any Post |
anyLong(), anyInt(), anyString() | Any value of the type (not null for anyString) |
eq(value) | Exactly this value |
contains("text"), startsWith("text") | A string with a fragment |
isNull(), notNull() | null / not null |
argThat(p -> ...) | Your own condition |
// src/test/java/com/example/blog/PostServiceMatchersTest.java
@Test
void anyIdReturnsTheSamePost() {
when(repository.findById(anyLong()))
.thenReturn(Optional.of(new Post(5L, "Hello", "anna", false)));
assertEquals("Hello", service.find(1L).title());
assertEquals("Hello", service.find(42L).title());
}
@Test
void notificationMentionsTitle() {
when(repository.findById(1L))
.thenReturn(Optional.of(new Post(1L, "Hello", "anna", false)));
service.publish(1L);
verify(notifier).send(eq("anna"), contains("Hello"));
verify(notifier, never()).send(eq("oleg"), anyString());
}The first test stubs the repository for any id. The second checks that the notification went to Anna and mentions the title, without repeating the whole text. The setup is the same as in the previous lesson: repository, notifier and service are fields.
The result depends on the argument: thenAnswer
A real repository returns the saved post with a new id. thenAnswer computes the result from the arguments of the call:
// 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) is the first argument passed to save().
ArgumentCaptor
create() builds the Post object inside the service. To check its fields, catch it on the way into the repository:
// 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(" Hello ", "anna");
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
verify(repository).save(captor.capture());
Post saved = captor.getValue();
assertNull(saved.id());
assertEquals("Hello", saved.title());
assertFalse(saved.published());
}captor.capture() in verify works as a matcher that accepts any argument and remembers it. getValue() returns the last caught value, getAllValues() — all of them if there were several calls. The test checks that the service saved a draft without an id and with a trimmed title.
Common mistake
If one argument uses a matcher, all arguments of this call must be matchers. Mixing them with plain values:
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.Wrap the plain value in eq(): verify(notifier).send(eq("anna"), anyString()).