Mockito: Mocking, Spying, Stubbing & Argument Captors
Mockito is the most widely used Java mocking framework. It lets you replace real collaborators with test doubles: mocks that record calls and return configured values, or spies that wrap real objects and selectively stub specific methods. This article covers the full Mockito API used in everyday testing.
Contents
Dependency & Setup
Spring Boot's spring-boot-starter-testmockito-junit-jupiter
<!-- Spring Boot — Mockito already included -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Standalone -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
Enable Mockito annotations in JUnit 5 tests by adding @ExtendWith(MockitoExtension.class)
Creating Mocks
Create mocks with the @MockMockito.mock()@InjectMocks
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.Assertions;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
OrderRepository orderRepo; // Mockito creates a mock implementation
@Mock
EmailService emailService;
@InjectMocks
OrderService orderService; // Mockito injects the mocks above via constructor/setter/field
@Test
void placesOrder() {
Order saved = new Order("o1", "Widget", 2);
Mockito.when(orderRepo.save(Mockito.any())).thenReturn(saved);
Order result = orderService.place("Widget", 2);
Assertions.assertEquals("o1", result.id());
}
}
@InjectMocks@InjectMocksStubbing: when/thenReturn
Use when(mock.method(args)).thenReturn(value)thenReturn
@Test
void stubbingExamples() {
UserRepository repo = Mockito.mock(UserRepository.class);
// Always return the same value
Mockito.when(repo.findById(1L))
.thenReturn(Optional.of(new User(1L, "Alice")));
// Return different values on successive calls
Mockito.when(repo.count())
.thenReturn(0L) // first call
.thenReturn(1L) // second call
.thenReturn(2L); // third and subsequent calls
// Compute a value dynamically with thenAnswer
Mockito.when(repo.findById(Mockito.anyLong()))
.thenAnswer(inv -> {
long id = inv.getArgument(0);
return Optional.of(new User(id, "User-" + id));
});
Assertions.assertEquals("User-42", repo.findById(42L).get().name());
}
Stubbing Exceptions & Void Methods
Use thenThrowvoiddoThrow(...).when(mock).method()
@Test
void stubbingExceptions() {
PaymentGateway gateway = Mockito.mock(PaymentGateway.class);
// Throw on a non-void method
Mockito.when(gateway.charge(Mockito.anyDouble()))
.thenThrow(new PaymentException("Declined"));
Assertions.assertThrows(PaymentException.class, () -> gateway.charge(50.0));
}
@Test
void stubbingVoidMethods() {
NotificationService notifier = Mockito.mock(NotificationService.class);
// void methods need doThrow/doNothing/doAnswer
Mockito.doThrow(new RuntimeException("SMTP down"))
.when(notifier).sendEmail(Mockito.anyString());
Mockito.doNothing()
.when(notifier).sendSms(Mockito.anyString()); // explicit no-op (default for void)
Assertions.assertThrows(RuntimeException.class, () -> notifier.sendEmail("a@b.com"));
}
Spies: Partial Mocking
A @Spy
@ExtendWith(MockitoExtension.class)
class PricingServiceTest {
@Spy
PricingService pricingService = new PricingService(new TaxConfig(0.2));
@Test
void spyCallsRealMethodByDefault() {
// Calls the real calculateBase() implementation
double base = pricingService.calculateBase("Widget", 3);
Assertions.assertTrue(base > 0);
}
@Test
void spyCanStubSpecificMethod() {
// Override just one method, rest are real
Mockito.doReturn(100.0).when(pricingService).calculateBase(Mockito.anyString(), Mockito.anyInt());
double total = pricingService.calculateTotal("Widget", 3); // uses stubbed base
Assertions.assertEquals(120.0, total); // 100 * 1.2 tax
}
}
doReturn(...).when(spy).method()when(spy.method()).thenReturn(...)Verifying Interactions
Use verify()
@Test
void verificationExamples() {
UserRepository repo = Mockito.mock(UserRepository.class);
AuditLogger audit = Mockito.mock(AuditLogger.class);
UserService svc = new UserService(repo, audit);
svc.deleteUser(42L);
// Was called exactly once with this argument
Mockito.verify(repo, Mockito.times(1)).deleteById(42L);
// Was called at least once
Mockito.verify(audit, Mockito.atLeastOnce()).log(Mockito.anyString());
// Was never called
Mockito.verify(repo, Mockito.never()).save(Mockito.any());
// No other interactions happened on this mock
Mockito.verifyNoMoreInteractions(repo);
}
@Test
void verifyOrder() {
DataSource ds = Mockito.mock(DataSource.class);
Notifier notifier = Mockito.mock(Notifier.class);
BatchJob job = new BatchJob(ds, notifier);
job.run();
InOrder inOrder = Mockito.inOrder(ds, notifier);
inOrder.verify(ds).connect();
inOrder.verify(ds).process();
inOrder.verify(notifier).send("done");
}
ArgumentCaptor
ArgumentCaptor
@ExtendWith(MockitoExtension.class)
class OrderServiceCaptorTest {
@Mock OrderRepository repo;
@Captor ArgumentCaptor<Order> orderCaptor;
@InjectMocks OrderService orderService;
@Test
void capturesOrderOnSave() {
orderService.place("Gadget", 5);
Mockito.verify(repo).save(orderCaptor.capture());
Order saved = orderCaptor.getValue();
Assertions.assertEquals("Gadget", saved.product());
Assertions.assertEquals(5, saved.quantity());
Assertions.assertNotNull(saved.createdAt());
}
}
Argument Matchers
Argument matchers let stubs and verifications match arguments flexibly. When using matchers, all arguments in the same method call must be matchers: mix matchers and literal values using eq()
UserRepository repo = Mockito.mock(UserRepository.class);
// Common matchers
Mockito.when(repo.findByEmail(Mockito.anyString()))
.thenReturn(Optional.empty());
Mockito.when(repo.findByAgeGreaterThan(Mockito.intThat(age -> age >= 18)))
.thenReturn(List.of(new User(1L, "Alice")));
// Mixing literal and matcher — use eq() for the literal
Mockito.when(repo.findByNameAndCity(Mockito.eq("Alice"), Mockito.anyString()))
.thenReturn(Optional.of(new User(1L, "Alice")));
// Custom matcher with argThat
Mockito.verify(repo).save(Mockito.argThat(
order -> order.quantity() > 0 && order.product() != null
));
| Matcher | Matches |
|---|---|
any()any(Class) | Anything, including null |
anyString()anyInt() | Any non-null value of that type |
eq(value) | Equals the given value |
isNull()isNotNull() | Null / non-null |
startsWith(str)contains(str) | String prefix / substring |
argThat(predicate) | Custom predicate |
intThat(predicate)longThat() | Custom predicate for primitives |