Mockito Advanced: Argument Captors and Spies
Capture method arguments with ArgumentCaptor and partially mock real objects with Mockito.spy.
Mockito Advanced: Argument Captors and Spies is a free Java Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
ArgumentCaptor: Capturing Method Arguments
ArgumentCaptor captures the arguments passed to a mocked method so you can assert on them. Useful when the argument is built inside the system under test and you cannot access it directly.
Creating and Using ArgumentCaptor
Create a captor for the argument type, pass captor.capture() as the argument matcher in verify(), then call captor.getValue() to get the captured argument.
@Test
void sends_correct_email() {
ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class);
userService.register(new CreateUserRequest("alice@example.com"));
verify(emailService).send(captor.capture());
EmailMessage msg = captor.getValue();
assertEquals("alice@example.com", msg.getTo());
assertTrue(msg.getSubject().contains("Welcome"));
}@Captor Annotation
Use @Captor with @ExtendWith(MockitoExtension.class) to avoid verbose ArgumentCaptor.forClass() calls.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock EmailService emailService;
@Captor ArgumentCaptor<EmailMessage> emailCaptor;
@Test
void captures_email() {
// ...
verify(emailService).send(emailCaptor.capture());
assertEquals("Welcome!", emailCaptor.getValue().getSubject());
}
}Capturing Multiple Calls
Use captor.getAllValues() when the method is called multiple times. Returns a list of all captured values in call order.
verify(emailService, times(3)).send(emailCaptor.capture());
List<EmailMessage> messages = emailCaptor.getAllValues();
assertEquals(3, messages.size());
assertEquals("Confirmation", messages.get(0).getSubject());Mockito Spies: Partial Mocking
Mockito.spy() wraps a real object. All methods behave normally unless you explicitly stub them. Use when you need to test a class that has some hard-to-test dependencies but mostly works.
List<String> realList = new ArrayList<>();
List<String> spy = Mockito.spy(realList);
spy.add("hello"); // calls real ArrayList.add()
System.out.println(spy.size()); // 1 — real method called
doReturn(42).when(spy).size(); // stub just size()
System.out.println(spy.size()); // 42 — stubbed@Spy Annotation
Use @Spy with @ExtendWith(MockitoExtension.class) to create spies. The field must be initialized (either inline or via @Spy UserService svc = new UserService(repo);).
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Spy
OrderService orderService = new OrderService(mockRepo, mockEmail);
}Spying on Spring Beans with @SpyBean
In Spring Boot tests, @SpyBean wraps an existing Spring bean in a Mockito spy — real behavior unless stubbed. Pairs with @SpringBootTest.
@SpringBootTest
class IntegrationTest {
@SpyBean EmailService emailService;
@Test
void order_sends_email() {
orderService.placeOrder(cart);
verify(emailService).sendConfirmation(any());
}
}doReturn vs when().thenReturn() for Spies
For spies, use doReturn(value).when(spy).method() instead of when(spy.method()).thenReturn(value). The when(spy.method()) form actually calls the real method, which may cause side effects.
// Correct for spies:
doReturn(List.of()).when(spy).findAll();
// WRONG for spies — calls real findAll() first:
when(spy.findAll()).thenReturn(List.of()); // real method called!Verifying No Interactions
Use verifyNoInteractions(mock) to assert a mock was never called, and verifyNoMoreInteractions(mock) after all expected calls to catch unexpected ones.
verifyNoInteractions(emailService); // never called
verify(repo).save(any());
verifyNoMoreInteractions(repo); // save() was the only interactionInOrder Verification
Use InOrder to assert that mocked methods were called in a specific order.
InOrder inOrder = inOrder(repo, emailService);
inOrder.verify(repo).save(any());
inOrder.verify(emailService).send(any()); // save happened BEFORE sendArgument Matchers
Combine captors with matchers: any(), eq(), anyString(), argThat(predicate). If any argument uses a matcher, all must use matchers.
verify(repo).save(argThat(user ->
user.getEmail().endsWith("@example.com") &&
user.getStatus() == UserStatus.ACTIVE));Quick Check
Why use doReturn instead of when().thenReturn() when stubbing a spy?
Recap
ArgumentCaptor captures arguments for assertion after the call. Spies wrap real objects and delegate unless stubbed. Use doReturn for spies. @SpyBean for Spring integration tests. Verify call order with InOrder.
Frequently asked questions
Is the “Mockito Advanced: Argument Captors and Spies” lesson free?
Yes — the full text of “Mockito Advanced: Argument Captors and Spies” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Mockito Advanced: Argument Captors and Spies”?
Capture method arguments with ArgumentCaptor and partially mock real objects with Mockito.spy. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Mockito Advanced: Argument Captors and Spies” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Parameterized Tests with @CsvSource and @MethodSource
- Mockito Advanced: Argument Captors and Spies
- Spring Boot Test Slices: @WebMvcTest and @DataJpaTest
- Testcontainers: Real Database Integration Tests