Spring Boot
Mockito
Mockito is the mocking framework bundled with spring-boot-starter-test. It creates test doubles — mocks, spies, and stubs — that simulate collaborator behaviour without real implementations. This entry covers mock creation, stubbing, argument matchers, argument captors, verification, spies, strict mocking, and common pitfalls.
Mock Creation and Injection
Create mocks with mock() or @Mock. @InjectMocks instantiates the class under test and injects @Mock fields by constructor, setter, or field injection. @ExtendWith(MockitoExtension.class) activates the annotations — no manual Mockito.openMocks() call needed.
Java
// ── Option 1: @ExtendWith(MockitoExtension.class) — recommended ──────
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepo;
@Mock
private PasswordEncoder encoder;
@Mock
private EmailService emailService;
// Mockito injects the @Mock fields into UserService
// via constructor injection (preferred),
// then setter injection, then field injection
@InjectMocks
private UserService userService;
@Test
void register_savesUser() {
when(userRepo.existsByEmail(any())).thenReturn(false);
when(encoder.encode(any())).thenReturn("$2a$12$hashed");
when(userRepo.save(any())).thenAnswer(inv -> {
User u = inv.getArgument(0);
u.setId(1L);
return u;
});
UserResponse response = userService.register(
new RegisterRequest("Alice",
"alice@example.com", "Pass123!"));
assertThat(response.id()).isEqualTo(1L);
}
}
// ── Option 2: Programmatic mock creation ─────────────────────────────
class OrderServiceTest {
private final OrderRepository orderRepo =
mock(OrderRepository.class);
private final OrderService orderService =
new OrderService(orderRepo);
@Test
void findById_returnsOrder() {
when(orderRepo.findById(1L))
.thenReturn(Optional.of(buildOrder(1L)));
OrderResponse response = orderService.findById(1L, 1L);
assertThat(response.id()).isEqualTo(1L);
}
}
// ── @MockBean — Spring context mock ───────────────────────────────────
// Use in @SpringBootTest or @WebMvcTest — replaces the bean in the
// context and resets after each test class
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private OrderService orderService; // Spring context
@Test
void findById_delegates_toService() throws Exception {
when(orderService.findById(eq(1L), any()))
.thenReturn(buildOrderResponse(1L));
mockMvc.perform(get("/api/v1/orders/1")
.with(user("alice").roles("USER")))
.andExpect(status().isOk());
}
}Stubbing — when/thenReturn/thenThrow
Stubbing defines what a mock returns when a specific method is called. Build stubs with when().thenReturn(), when().thenThrow(), when().thenAnswer(), and when().thenCallRealMethod(). Multiple thenReturn() calls produce different values on successive invocations. Unstubbed methods return safe defaults — null for objects, 0 for primitives, empty collections.
Java
@ExtendWith(MockitoExtension.class)
class StubbingExamplesTest {
@Mock private ProductRepository productRepo;
@Mock private PricingService pricingService;
@InjectMocks private ProductService productService;
// ── thenReturn — fixed return value ───────────────────────────────
@Test
void findById_returnsProduct() {
Product product = buildProduct(1L, "Widget");
when(productRepo.findById(1L))
.thenReturn(Optional.of(product));
ProductResponse response = productService.findById(1L);
assertThat(response.name()).isEqualTo("Widget");
}
// ── thenThrow — throw an exception ───────────────────────────────
@Test
void findById_throws_whenNotFound() {
when(productRepo.findById(99L))
.thenReturn(Optional.empty());
assertThatThrownBy(() -> productService.findById(99L))
.isInstanceOf(ProductNotFoundException.class);
}
// ── thenAnswer — dynamic response based on arguments ─────────────
@Test
void saveAll_returnsWithGeneratedIds() {
when(productRepo.save(any(Product.class)))
.thenAnswer(invocation -> {
Product p = invocation.getArgument(0);
p.setId(new Random().nextLong(1, 1000));
return p;
});
Product saved = productService.create(buildRequest());
assertThat(saved.getId()).isPositive();
}
// ── Consecutive stubs — different on each call ────────────────────
@Test
void retry_succeedsOnThirdAttempt() {
when(pricingService.getPrice(1L))
.thenThrow(new ServiceUnavailableException("1st"))
.thenThrow(new ServiceUnavailableException("2nd"))
.thenReturn(new BigDecimal("9.99"));
BigDecimal price = productService.getPriceWithRetry(1L);
assertThat(price).isEqualByComparingTo("9.99");
}
// ── doReturn — for void methods and spies ─────────────────────────
@Test
void delete_callsRepository() {
doNothing().when(productRepo).deleteById(1L);
productService.delete(1L);
verify(productRepo).deleteById(1L);
}
// ── doThrow — stub void method to throw ───────────────────────────
@Test
void delete_propagatesException() {
doThrow(new DataIntegrityViolationException("FK violation"))
.when(productRepo).deleteById(1L);
assertThatThrownBy(() -> productService.delete(1L))
.isInstanceOf(ConflictException.class);
}
}Argument Matchers and Captors
Argument matchers relax the stub condition — any(), anyLong(), anyString(), argThat() — so the stub fires regardless of the specific value passed. ArgumentCaptor captures the actual argument passed to a mock for detailed assertion. Mix matchers carefully — if any parameter uses a matcher, all parameters must use matchers.
Java
@ExtendWith(MockitoExtension.class)
class ArgumentMatchersTest {
@Mock private OrderRepository orderRepo;
@Mock private NotificationService notificationService;
@InjectMocks private OrderService orderService;
// ── Argument matchers ─────────────────────────────────────────────
@Test
void create_savesAnyOrder() {
when(orderRepo.save(any(Order.class)))
.thenReturn(buildOrder(1L));
when(orderRepo.existsByUserId(anyLong()))
.thenReturn(false);
when(orderRepo.findByStatus(
eq(OrderStatus.PENDING), // exact match for status
any(Pageable.class))) // any pageable
.thenReturn(Page.empty());
orderService.create(buildRequest(), 1L);
verify(orderRepo).save(any(Order.class));
}
// ── argThat — custom predicate matcher ───────────────────────────
@Test
void create_savesOrderWithCorrectUserId() {
when(orderRepo.save(
argThat(order -> order.getUserId().equals(42L))))
.thenReturn(buildOrder(1L));
orderService.create(buildRequest(), 42L);
}
// ── ArgumentCaptor — inspect the saved object ─────────────────────
@Test
void create_persistsOrderWithCorrectFields() {
when(orderRepo.save(any(Order.class)))
.thenAnswer(inv -> {
Order o = inv.getArgument(0);
o.setId(1L);
return o;
});
CreateOrderRequest request = new CreateOrderRequest(
List.of(new OrderItemRequest(101L, 2,
new BigDecimal("9.99"))),
"123 Main St", "USD");
orderService.create(request, 42L);
// Capture the Order that was passed to save()
ArgumentCaptor<Order> orderCaptor =
ArgumentCaptor.forClass(Order.class);
verify(orderRepo).save(orderCaptor.capture());
Order saved = orderCaptor.getValue();
assertThat(saved.getUserId()).isEqualTo(42L);
assertThat(saved.getStatus())
.isEqualTo(OrderStatus.PENDING);
assertThat(saved.getTotal())
.isEqualByComparingTo("19.98");
assertThat(saved.getItems()).hasSize(1);
}
// ── Capture all invocations ────────────────────────────────────────
@Test
void createBatch_sendsNotificationForEachOrder() {
List<CreateOrderRequest> requests = List.of(
buildRequest(), buildRequest(), buildRequest());
when(orderRepo.save(any()))
.thenAnswer(inv -> buildOrder(new Random().nextLong()));
orderService.createBatch(requests, 1L);
ArgumentCaptor<String> emailCaptor =
ArgumentCaptor.forClass(String.class);
verify(notificationService, times(3))
.sendOrderConfirmation(emailCaptor.capture(),
any());
// All captures in order
List<String> captured = emailCaptor.getAllValues();
assertThat(captured).hasSize(3);
}
}Verification
verify() checks that a mock method was called with specific arguments and a specific number of times. Combine with times(), never(), atLeast(), atMost(), and inOrder() for precise interaction assertions. verifyNoMoreInteractions() fails if any unstubbed calls were made.
Java
@ExtendWith(MockitoExtension.class)
class VerificationTest {
@Mock private OrderRepository orderRepo;
@Mock private EmailService emailService;
@Mock private AuditService auditService;
@InjectMocks private OrderService orderService;
@Test
void delete_verifiesInteractions() {
Order order = buildOrder(1L, OrderStatus.PENDING);
when(orderRepo.findById(1L))
.thenReturn(Optional.of(order));
orderService.delete(1L, 1L);
// ── Exact call count ──────────────────────────────────────────
verify(orderRepo, times(1)).findById(1L);
verify(orderRepo, times(1)).delete(order);
// ── Never called ──────────────────────────────────────────────
verify(emailService, never())
.sendOrderCancellation(any());
// ── At least / at most ────────────────────────────────────────
verify(auditService, atLeastOnce())
.log(any(), any());
verify(auditService, atMost(2))
.log(any(), any());
}
@Test
void create_callsServicesInOrder() {
when(orderRepo.save(any()))
.thenReturn(buildOrder(1L));
orderService.create(buildRequest(), 1L);
// ── Verify call order ─────────────────────────────────────────
InOrder inOrder = inOrder(
orderRepo, emailService, auditService);
inOrder.verify(orderRepo).save(any());
inOrder.verify(emailService).sendOrderConfirmation(any(), any());
inOrder.verify(auditService).log(any(), any());
}
@Test
void findById_onlyCallsRepository_noSideEffects() {
when(orderRepo.findByIdAndUserId(1L, 1L))
.thenReturn(Optional.of(buildOrder(1L)));
orderService.findById(1L, 1L);
// Verify repo was called
verify(orderRepo).findByIdAndUserId(1L, 1L);
// Verify no unexpected interactions
verifyNoMoreInteractions(orderRepo);
verifyNoInteractions(emailService, auditService);
}
}Spies and Strict Mocking
A spy wraps a real object — only stubbed methods are intercepted; others delegate to the real implementation. Strict mocking (MockitoExtension default in strict mode) fails tests with unused stubs and argument mismatch warnings, keeping test code clean and preventing false-positive tests.
Java
@ExtendWith(MockitoExtension.class)
class SpiesAndStrictTest {
// ── Spy — real implementation with selective stubbing ─────────────
@Test
void spy_usesRealMethodUnlessStubbed() {
List<String> realList = new ArrayList<>(
List.of("apple", "banana", "cherry"));
List<String> spiedList = spy(realList);
// Real method used — returns actual size
assertThat(spiedList.size()).isEqualTo(3);
// Stub one method
doReturn(99).when(spiedList).size();
assertThat(spiedList.size()).isEqualTo(99);
// Real add() still works
spiedList.add("date");
assertThat(realList).hasSize(4);
}
// ── @Spy annotation ───────────────────────────────────────────────
@ExtendWith(MockitoExtension.class)
class OrderServiceSpyTest {
@Spy
private List<String> processedOrders = new ArrayList<>();
@Mock
private OrderRepository orderRepo;
@InjectMocks
private OrderService orderService;
@Test
void create_addsToProcessedList() {
when(orderRepo.save(any()))
.thenReturn(buildOrder(1L));
orderService.create(buildRequest(), 1L);
// Real add() called on the spy
assertThat(processedOrders).hasSize(1);
}
}
}
// ── Strict mocking configuration ──────────────────────────────────────
@ExtendWith(MockitoExtension.class)
// MockitoExtension uses STRICT_STUBS by default:
// - Detects unnecessary stubbing
// - Detects argument mismatch in stubbing
// - Fails the test with a clear message
// ── STRICT_STUBS catches: ──────────────────────────────────────────────
// 1. Unused stub: when(repo.findById(1L)).thenReturn(...)
// but findById(1L) is never called in the test
// 2. Argument mismatch: stub uses findById(1L) but call uses findById(2L)
//
// Both indicate a logic error in the test itself.
// ── Suppress strict check for one stub ───────────────────────────────
@Test
void lenient_stubForBaselineSetup() {
// Use lenient() when a stub is in @BeforeEach but not all
// tests invoke it
lenient().when(orderRepo.findById(anyLong()))
.thenReturn(Optional.of(buildOrder(1L)));
}