Spring BootMockito
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)));
}