Spring Boot

Mockito

Mockito is the mocking library bundled with spring-boot-starter-test. Use it to isolate the class under test from slow or non-deterministic collaborators such as repositories, HTTP clients, and mail senders. This entry covers mock creation, stubbing, matchers and captors, verification, spies, strict stubs, BDDMockito, @MockitoBean in Spring slices, static mocking, and the pitfalls that cause brittle tests. It targets Spring Boot 3.4+ (Mockito 5); older projects can swap @MockitoBean for @MockBean.

Mock Creation and Injection

Use @ExtendWith(MockitoExtension.class) with @Mock and @InjectMocks for plain unit tests. No Spring context starts, so these tests run in milliseconds. @InjectMocks tries constructor injection first (using the biggest constructor), then setter injection, then field injection. If it cannot satisfy a constructor argument it passes null without complaint, so prefer constructor injection in production code and check the test fails loudly when a dependency is missing. Mock your own collaborators at the boundary of the unit; do not mock value objects, records, or types you do not own (wrap those behind your own interface first).
Java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock UserRepository userRepo;
    @Mock PasswordEncoder encoder;

    @InjectMocks UserService userService; // new UserService(userRepo, encoder)

    @Test
    void register_savesHashedPassword() {
        when(userRepo.existsByEmail("alice@example.com")).thenReturn(false);
        when(encoder.encode("Pass123!")).thenReturn("hashed");
        when(userRepo.save(any(User.class)))
            .thenAnswer(inv -> inv.getArgument(0));

        UserResponse response = userService.register(
            new RegisterRequest("Alice", "alice@example.com", "Pass123!"));

        assertThat(response.email()).isEqualTo("alice@example.com");
    }
}

// Without the extension, create mocks by hand
class OrderServiceTest {
    private final OrderRepository orderRepo = mock(OrderRepository.class);
    private final OrderService orderService = new OrderService(orderRepo);
}

Stubbing with when and thenReturn

Stubbing tells a mock what to return for a given call. An unstubbed method returns a type-appropriate default: null for objects, 0 and false for primitives, and empty values for Optional, collections, maps, and streams. That last point matters: an unstubbed findById returns Optional.empty(), not null, so a missing stub often shows up as a "not found" branch rather than a NullPointerException. Stub only what the code path under test actually calls. Under strict stubs, anything extra fails the test.
Java
@Test
void findById_returnsProduct() {
    when(productRepo.findById(1L))
        .thenReturn(Optional.of(buildProduct(1L, "Widget")));

    assertThat(productService.findById(1L).name()).isEqualTo("Widget");
}

@Test
void findById_throwsWhenMissing() {
    when(productRepo.findById(99L)).thenReturn(Optional.empty());

    assertThatThrownBy(() -> productService.findById(99L))
        .isInstanceOf(ProductNotFoundException.class);
}

// Consecutive results: the last one repeats for later calls
@Test
void getPriceWithRetry_succeedsOnThirdAttempt() {
    when(pricingClient.getPrice(1L))
        .thenThrow(new ServiceUnavailableException("1st"))
        .thenThrow(new ServiceUnavailableException("2nd"))
        .thenReturn(new BigDecimal("9.99"));

    assertThat(productService.getPriceWithRetry(1L))
        .isEqualByComparingTo("9.99");
}

// thenAnswer: compute the result from the arguments
@Test
void create_returnsEntityWithGeneratedId() {
    when(productRepo.save(any(Product.class))).thenAnswer(inv -> {
        Product p = inv.getArgument(0);
        p.setId(7L);
        return p;
    });

    assertThat(productService.create(buildRequest()).id()).isEqualTo(7L);
}

// void methods cannot use when(...); use the do* family
@Test
void delete_translatesConstraintViolation() {
    doThrow(new DataIntegrityViolationException("FK violation"))
        .when(productRepo).deleteById(1L);

    assertThatThrownBy(() -> productService.delete(1L))
        .isInstanceOf(ConflictException.class);
}

Argument Matchers and Captors

Exact values make the strongest stubs, so reach for matchers only when the argument is not under your control (a generated ID, a timestamp, an object built inside the service). The rule that trips everyone up: if one argument uses a matcher, all arguments must use matchers. Wrap literals in eq(). When you need to assert on an object the service built internally, use an ArgumentCaptor and assert on its fields afterwards. That is usually better than a long argThat lambda, because a failing assertion points at the exact field.
Java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock OrderRepository orderRepo;
    @InjectMocks OrderService orderService;

    @Captor ArgumentCaptor<Order> orderCaptor;

    @Test
    void create_persistsOrderWithCorrectFields() {
        when(orderRepo.save(any(Order.class)))
            .thenAnswer(inv -> inv.getArgument(0));

        var request = new CreateOrderRequest(
            List.of(new OrderItemRequest(101L, 2, new BigDecimal("9.99"))),
            "123 Main St");

        orderService.create(request, 42L);

        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");
    }

    // Mixing raw values and matchers throws InvalidUseOfMatchersException
    @Test
    void matcherRule() {
        // BAD:  when(orderRepo.findByStatus(OrderStatus.PENDING, any(Pageable.class)))
        when(orderRepo.findByStatus(eq(OrderStatus.PENDING), any(Pageable.class)))
            .thenReturn(Page.empty());

        orderService.listPending(Pageable.unpaged());
    }
}

Verification

verify() asserts an interaction happened. Use it for side effects that have no return value, such as sending an email, publishing an event, or writing an audit record. Do not verify calls you already stubbed and whose results you assert on: the assertion proves the call happened, and the extra verify only couples the test to the implementation. verifyNoMoreInteractions() fails if any call on the mock has not been verified (calls to stubs that were used count as verified under strict stubs). Use it sparingly, because it makes tests break whenever you add a harmless call. Use inOrder() only when order is part of the contract, such as save before publish.
Java
@Test
void cancel_savesThenNotifies() {
    Order order = buildOrder(1L, OrderStatus.PENDING);
    when(orderRepo.findById(1L)).thenReturn(Optional.of(order));

    orderService.cancel(1L);

    InOrder inOrder = inOrder(orderRepo, emailService);
    inOrder.verify(orderRepo).save(order);
    inOrder.verify(emailService).sendCancellation(order.getUserEmail());

    verify(auditService, never()).logRefund(any());
    verifyNoMoreInteractions(emailService);
}

// Counts: times(n), never(), atLeastOnce(), atMost(n)
@Test
void createBatch_notifiesOncePerOrder() {
    when(orderRepo.save(any())).thenAnswer(inv -> inv.getArgument(0));

    orderService.createBatch(List.of(buildRequest(), buildRequest()), 1L);

    verify(notificationService, times(2))
        .sendOrderConfirmation(anyString(), any());
}

Spies and Strict Stubs

A spy wraps a real object: unstubbed methods run the real code, stubbed ones are intercepted. Treat spies as a last resort for legacy classes you cannot refactor. Always stub a spy with doReturn(...).when(spy).method(), because when(spy.method()) calls the real method first and can throw or cause side effects. MockitoExtension runs in strict-stubs mode: a stub that the test never uses throws UnnecessaryStubbingException, and calling a stubbed method with different arguments raises PotentialStubbingProblem. Both usually mean the test and the code have drifted apart. Delete the stub, do not silence the check. Use lenient() only for shared setup that some tests legitimately skip.
Java
@ExtendWith(MockitoExtension.class)
class SpiesAndStrictStubsTest {

    @Mock OrderRepository orderRepo;
    @InjectMocks OrderService orderService;

    @BeforeEach
    void baseline() {
        // Shared stub that not every test uses
        lenient().when(orderRepo.findById(1L))
            .thenReturn(Optional.of(buildOrder(1L)));
    }

    @Test
    void spy_usesRealMethodsUnlessStubbed() {
        List<String> spied = spy(new ArrayList<>(List.of("a", "b")));

        doReturn(99).when(spied).size();   // safe
        // when(spied.size()).thenReturn(99) would invoke the real size() first

        assertThat(spied.size()).isEqualTo(99);
        spied.add("c");                     // real add still runs
        assertThat(spied.get(2)).isEqualTo("c");
    }
}

BDDMockito

BDDMockito renames the same operations to match given/when/then test structure: given().willReturn() replaces when().thenReturn(), and then(mock).should() replaces verify(mock). Behaviour is identical, so the choice is purely about readability. It avoids the awkward situation where the "when" in your stub is not the "when" of your test. Pick one style per codebase and stay consistent.
Java
import static org.mockito.BDDMockito.*;

@Test
void register_rejectsDuplicateEmail() {
    given(userRepo.existsByEmail("alice@example.com")).willReturn(true);

    assertThatThrownBy(() -> userService.register(aliceRequest()))
        .isInstanceOf(DuplicateEmailException.class);

    then(userRepo).should(never()).save(any());
}

Mocking Beans in Spring Tests

In @WebMvcTest, @DataJpaTest, and @SpringBootTest slices, use @MockitoBean to replace a bean in the application context. It supersedes @MockBean, which was deprecated in Spring Boot 3.4 and removed in Boot 4. Mocks are reset after each test method. The real cost is context caching: every distinct combination of @MockitoBean declarations creates a new cached ApplicationContext, so scattering different mocks across test classes slows the suite sharply. Put shared mocks in a common abstract base class or declare the same set across related tests. In plain unit tests that do not need Spring, use @Mock instead.
Java
// Spring Boot 3.4+
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired MockMvc mockMvc;
    @MockitoBean OrderService orderService;

    @Test
    @WithMockUser(roles = "USER")
    void findById_returnsOrder() throws Exception {
        when(orderService.findById(eq(1L), any()))
            .thenReturn(buildOrderResponse(1L));

        mockMvc.perform(get("/api/v1/orders/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1));
    }
}

Static Mocks and Deep Stubs

Mockito 5 uses the inline mock maker by default, so final classes and final methods are mockable out of the box, and mockStatic works without extra dependencies. A static mock is thread-local and must be closed, so always use try-with-resources. Prefer injecting a java.time.Clock or a small wrapper over mocking statics like LocalDate.now() or UUID.randomUUID(). RETURNS_DEEP_STUBS lets you stub a.b().c() in one go, but it usually signals a Law of Demeter violation in the production code. Fix the design instead.
Java
@Test
void generateToken_usesRandomUuid() {
    UUID fixed = UUID.fromString("00000000-0000-0000-0000-000000000001");

    try (MockedStatic<UUID> uuid = mockStatic(UUID.class)) {
        uuid.when(UUID::randomUUID).thenReturn(fixed);

        assertThat(tokenService.generate())
            .contains("00000000-0000-0000-0000-000000000001");
    }
}

// Preferred: inject a Clock instead of mocking static time
Clock fixedClock = Clock.fixed(
    Instant.parse("2026-01-01T00:00:00Z"), ZoneOffset.UTC);
OrderService service = new OrderService(orderRepo, fixedClock);

Common Pitfalls

Most brittle Mockito tests come from a short list of mistakes. Over-mocking: if the test stubs five collaborators to check a one-line mapping, use real objects (or a slice test) instead. Verifying implementation details: assert on outcomes first and verify only meaningful side effects. Mocking what you do not own: wrap third-party clients in your own interface and mock that. Forgetting that @InjectMocks silently leaves unmatched dependencies null. Mixing matchers and raw values. Using when() on a spy. Sprinkling lenient() to hide UnnecessaryStubbingException instead of removing dead stubs. And creating a different set of @MockitoBean declarations in every test class, which fragments the context cache.
Java
// BAD: tests the mock, not the code
when(mapper.toDto(entity)).thenReturn(dto);
assertThat(mapper.toDto(entity)).isEqualTo(dto);

// BAD: couples the test to internals, adds nothing the assertion lacks
when(repo.findById(1L)).thenReturn(Optional.of(user));
assertThat(service.find(1L).name()).isEqualTo("Alice");
verify(repo).findById(1L);

// GOOD: verify only the side effect that has no return value
userService.deactivate(1L);
verify(eventPublisher).publish(any(UserDeactivated.class));