Advanced Mockito features for partial mocking, capturing arguments, and mocking static methods.

Spy — Partial Mock

A spy wraps a real object, allowing you to stub specific methods while calling real ones:

  List<String> list = new ArrayList<>(List.of("a", "b"));
List<String> spy = spy(list);

spy.add("c");                          // real method — list now has 3 items
when(spy.size()).thenReturn(100);      // stub size()

assertEquals(100, spy.size());         // stubbed
assertEquals(3, list.size());          // real list modified
  

Important: Use doReturn().when(spy) for spies, not when().thenReturn() — avoids calling the real method during stubbing.

  doReturn(100).when(spy).size();
  

ArgumentCaptor

Capture arguments passed to mocked methods for detailed assertions:

  @Test
void shouldSaveUserWithCorrectData() {
    ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);

    userService.createUser("Alice", "[email protected]");

    verify(repository).save(captor.capture());
    User saved = captor.getValue();
    assertEquals("Alice", saved.getName());
    assertEquals("[email protected]", saved.getEmail());
}
  

Multiple captures:

  verify(repository, times(2)).save(captor.capture());
List<User> allSaved = captor.getAllValues();
assertEquals(2, allSaved.size());
  

Answer — Custom Stub Logic

  when(repository.findById(anyLong())).thenAnswer(invocation -> {
    Long id = invocation.getArgument(0);
    return Optional.of(new User(id, "User-" + id));
});

when(repository.save(any())).thenAnswer(invocation -> {
    User user = invocation.getArgument(0);
    user.setId(UUID.randomUUID().getMostSignificantBits());
    return user;
});
  

Static Method Mocking (Mockito 3.4+)

Requires mockito-inline:

  try (MockedStatic<IdGenerator> mocked = mockStatic(IdGenerator.class)) {
    mocked.when(IdGenerator::nextId).thenReturn(42L);

    User user = userService.createUser("Alice");
    assertEquals(42L, user.getId());
}
  

Mock Construction (Mockito 3.5+)

Mock object construction:

  try (MockedConstruction<ExternalClient> mocked =
        mockConstruction(ExternalClient.class, (mock, context) -> {
    when(mock.fetchData()).thenReturn("mocked data");
})) {
    Service service = new Service();
    assertEquals("mocked data", service.process());
    assertEquals(1, mocked.constructed().size());
}
  

Timeout Verification

  verify(repository, timeout(1000)).save(any());
verify(repository, after(500).times(2)).findAll();
  

Reset Mocks

  reset(repository); // clears stubbing and interaction history
  

Use sparingly — usually indicates tests are not independent.

Best Practices

  • Prefer @Mock over @Spy — spies are for partial mocking only
  • Use ArgumentCaptor when you need to inspect what was passed to a mock
  • Always close MockedStatic and MockedConstruction (use try-with-resources)
  • Static mocking is a last resort — consider refactoring for testability
  • Avoid mocking types you do not own — wrap them in an adapter instead