Mockito and Test Doubles
IntermediateMockito is the standard Java mocking framework — create mocks, stub behaviour, and verify interactions to isolate units under test.
Overview
Mockito lets you create test doubles (mocks, stubs, spies) to isolate the unit under test from its dependencies. Mock objects record calls and can verify interactions. Stubs return configured values. Spies wrap real objects and selectively stub methods. Combined with JUnit 5 and AssertJ, Mockito forms the standard Java unit testing stack.
Creating Mocks and Stubbing
Mockito.mock() creates a mock. when().thenReturn() stubs a method call. @Mock and @InjectMocks annotations with MockitoExtension reduce boilerplate.
By default, unstubbed methods return safe defaults: 0 for numbers, false for booleans, empty collections, null for objects.
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository repo; // auto-created mock
@Mock
EmailService emailService;
@InjectMocks
UserService service; // repo + emailService injected automatically
@Test
void findByIdReturnsUser() {
// Stub: when repo.findById(42) is called, return this user
User alice = new User(42, "Alice", "alice@example.com");
when(repo.findById(42)).thenReturn(Optional.of(alice));
User result = service.findById(42);
assertThat(result.getName()).isEqualTo("Alice");
}
@Test
void findByIdThrowsWhenNotFound() {
when(repo.findById(anyInt())).thenReturn(Optional.empty());
assertThatThrownBy(() -> service.findById(99))
.isInstanceOf(UserNotFoundException.class);
}
}Verifying Interactions
verify() asserts that a method was called with specific arguments. Use times(), never(), atLeastOnce(), atMost() to check call counts.
ArgumentCaptor captures arguments passed to a mock for inspection — useful when the argument is constructed inside the method under test.
@Test
void placeOrderSendsConfirmationEmail() {
User user = new User(1, "Alice", "alice@example.com");
when(repo.findById(1)).thenReturn(Optional.of(user));
service.placeOrder(1, "item-42", 2);
// Verify email was sent exactly once
verify(emailService, times(1))
.sendConfirmation(eq("alice@example.com"), anyString());
// Verify repository was never asked to delete anything
verify(repo, never()).delete(any());
}
@Test
void captureOrderSentToRepo() {
// Capture the Order object saved to the repo
ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
service.placeOrder(1, "item-42", 2);
verify(repo).save(captor.capture());
Order saved = captor.getValue();
assertThat(saved.getItemId()).isEqualTo("item-42");
assertThat(saved.getQuantity()).isEqualTo(2);
assertThat(saved.getStatus()).isEqualTo(OrderStatus.PENDING);
}Spies, Answers, and Common Pitfalls
A spy wraps a real object — real methods are called by default, but you can stub specific ones. Use @Spy for partial mocking when most real behaviour is wanted.
Common pitfalls: calling when() on a void method (use doReturn/doThrow instead), stubbing methods that are not called (UnnecessaryStubbingException in strict mode), and mocking final classes (requires mockito-inline).
// Spy — real object with selective stubbing
@Spy
List<String> spyList = new ArrayList<>();
@Test
void spyExample() {
spyList.add("one");
spyList.add("two");
// Real method called
assertThat(spyList).hasSize(2);
// Stub one method
doReturn(100).when(spyList).size();
assertThat(spyList).hasSize(100); // stubbed
}
// Stubbing void methods
doNothing().when(emailService).sendConfirmation(anyString(), anyString());
doThrow(new RuntimeException("SMTP down"))
.when(emailService).sendConfirmation(eq("bad@email"), anyString());
// Returning different values on successive calls
when(repo.findById(1))
.thenReturn(Optional.of(user)) // first call
.thenReturn(Optional.empty()); // second call
// Answer — compute return value dynamically
when(repo.save(any(Order.class)))
.thenAnswer(inv -> {
Order order = inv.getArgument(0);
order.setId(UUID.randomUUID().toString());
return order;
});Key Points to Remember
- @Mock creates a mock; @InjectMocks creates the class under test and injects mocks.
- when().thenReturn() stubs; verify() asserts interactions; ArgumentCaptor captures arguments.
- Spies wrap real objects — real methods run by default, selective stubbing possible.
- Use doReturn/doThrow for void methods and spies to avoid calling the real method.
- Strict stubbing (MockitoExtension default) fails on unused stubs — keeps tests clean.
Practice Mockito and Test Doubles in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between a mock and a spy in Mockito?
What is ArgumentCaptor and when do you use it?
Why should you use @InjectMocks instead of manually constructing the class under test?
What is the difference between verify(mock, times(1)) and verify(mock)?
What is strict stubbing and why is it useful?
Ask Aria about Mockito and Test Doubles
Your personal AI tutor — ask anything about this concept