Mockito
Mockito lets you replace a class’s collaborators with controllable test doubles, so you can test one class in complete isolation — no Spring context, no database, no network. It is bundled with spring-boot-starter-test and is the foundation of fast unit tests.
Why mock collaborators
A service usually depends on a repository, a mail client, or another service. In a unit test you do not want to hit a real database or send real email — you want to control exactly what those collaborators return and then assert how your class reacts. A mock is an object that records calls and returns whatever you tell it to.
Creating mocks
You can create a mock programmatically with Mockito.mock(...), but in practice you use @Mock plus @InjectMocks and let the MockitoExtension wire them.
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
OrderRepository repository; // a fake repository
@Mock
EmailClient emailClient; // a fake mail client
@InjectMocks
OrderService service; // mocks injected into this
}
@ExtendWith(MockitoExtension.class) initializes the @Mock fields and constructs @InjectMocks with them. No Spring context starts, so the test is fast.
Stubbing with when/thenReturn
By default a mock returns “empty” values — null, 0, an empty Optional, an empty list. Use when(...).thenReturn(...) to define behavior.
when(repository.findById(1L))
.thenReturn(Optional.of(new Order(1L, "PAID")));
when(repository.count()).thenReturn(42L);
// Stub a thrown exception
when(repository.findById(999L))
.thenThrow(new EntityNotFoundException("not found"));
For void methods use doThrow(...).when(mock).method().
A complete pure unit test
Here is the service under test and a full test that stubs its repository and verifies a side effect.
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repository;
private final EmailClient emailClient;
public Order place(String customer) {
Order order = repository.save(new Order(customer, "NEW"));
emailClient.sendConfirmation(customer, order.getId());
return order;
}
}
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository repository;
@Mock EmailClient emailClient;
@InjectMocks OrderService service;
@Test
void placeSavesOrderAndSendsEmail() {
var saved = new Order(7L, "alice", "NEW");
when(repository.save(any(Order.class))).thenReturn(saved);
Order result = service.place("alice");
assertThat(result.getId()).isEqualTo(7L);
verify(emailClient).sendConfirmation("alice", 7L);
}
}
Output:
OrderServiceTest > placeSavesOrderAndSendsEmail() PASSED
BUILD SUCCESS
Verifying interactions
verify(...) asserts that a method was (or was not) called, and how many times.
verify(emailClient).sendConfirmation("alice", 7L); // exactly once (default)
verify(repository, times(1)).save(any());
verify(emailClient, never()).sendConfirmation(eq("bob"), anyLong());
verify(repository, atLeastOnce()).count();
verifyNoMoreInteractions(emailClient);
To inspect the exact argument passed, capture it with @Captor / ArgumentCaptor.
@Captor ArgumentCaptor<Order> orderCaptor;
verify(repository).save(orderCaptor.capture());
assertThat(orderCaptor.getValue().getStatus()).isEqualTo("NEW");
Argument matchers
When you stub or verify with a matcher like any(), all arguments must be matchers. Mix raw values in via eq(...).
| Matcher | Matches |
|---|---|
any() / any(Type.class) | Any value of that type |
eq(value) | A specific value (use alongside other matchers) |
anyLong(), anyString() | Any primitive/string |
argThat(predicate) | A custom condition |
isNull() / isNotNull() | Null checks |
// Correct: every argument is a matcher
when(repository.search(eq("alice"), anyInt())).thenReturn(List.of());
Spies — partial mocks
A spy wraps a real object: real methods run unless you stub them. Use sparingly, for legacy code or when you need most behavior intact.
List<String> list = spy(new ArrayList<>());
list.add("a"); // real method runs
when(list.size()).thenReturn(100); // stub one method
assertThat(list.size()).isEqualTo(100);
assertThat(list.get(0)).isEqualTo("a");
Warning: When stubbing a spy, prefer
doReturn(...).when(spy).method()overwhen(spy.method())...— the latter actually calls the real method while setting up the stub, which can have side effects.
Tip: If you find yourself building a huge web of mocks for one test, the class under test is probably doing too much. That pressure is a useful design signal — split the responsibilities.