0Pricing
Java Academy · Lesson

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 interaction

InOrder 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 send

Argument 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

  1. Parameterized Tests with @CsvSource and @MethodSource
  2. Mockito Advanced: Argument Captors and Spies
  3. Spring Boot Test Slices: @WebMvcTest and @DataJpaTest
  4. Testcontainers: Real Database Integration Tests
← Back to Java Academy