| name | mockito |
| description | [Applies to: **/*.java] This guide provides definitive best practices for writing robust, maintainable, and readable unit tests using Mockito with JUnit 5, focusing on modern patterns and common pitfalls. |
| source | cursor_mdc |
Mockito Best Practices
Mockito is the indispensable mocking framework for Java unit testing. Adhere to these guidelines to ensure your tests are clear, reliable, and resistant to refactoring.
1. Standardize Test Setup with JUnit 5 & Annotations
Always use JUnit 5's @ExtendWith for Mockito integration and leverage annotations for mock initialization. This keeps your setup concise and automatic.
❌ BAD: Manual setup or old JUnit 4 runner
UserRepository repo = mock(UserRepository.class);
UserService service = new UserService(repo);
✅ GOOD: JUnit 5 @ExtendWith and annotations
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
}
2. One Test, One Behavior
Each test method must verify a single, distinct behavior. This makes tests easier to debug and provides clear documentation.
❌ BAD: Multiple unrelated assertions in one test
@Test
void processOrder_createsUser_addsPoints_sendsEmail() {
orderService.placeOrder(orderRequest);
verify(userRepository).save(any(User.class));
verify(loyaltyService).addPoints(anyString(), anyInt());
verify(notificationService).sendEmail(anyString(), anyString());
}
✅ GOOD: Focused tests
@Test
void shouldSaveUserWhenOrderIsPlaced() {
orderService.placeOrder(orderRequest);
verify(userRepository).save(any(User.class));
}
@Test
void shouldAddLoyaltyPointsForEligibleUsers() {
when(loyaltyPolicy.isEligible(any())).thenReturn(true);
orderService.placeOrder(orderRequest);
verify(loyaltyService).addPoints(anyString(), anyInt());
}
3. Verify Outcomes, Not Implementation Details
Only verify interactions that are part of the contract being tested. Avoid verifying internal method calls or private state.
❌ BAD: Verifying internal helper method calls
@Test
void shouldCalculateTaxWhenPlacingOrder() {
orderService.placeOrder(orderRequest);
verify(taxCalculator).calculateTax(any());
}
✅ GOOD: Verifying the observable outcome
@Test
void shouldReturnOrderWithCorrectTotalIncludingTax() {
when(taxCalculator.calculateTax(any())).thenReturn(BigDecimal.valueOf(10));
Order result = orderService.placeOrder(orderRequest);
assertEquals(BigDecimal.valueOf(110), result.getTotalAmount());
}
4. Handle UnnecessaryStubbingException Correctly
Mockito's strict stubbing (default with MockitoExtension) catches unused stubs. This prevents dead code in tests. Resolve it by removing unused stubs. Use lenient() only when absolutely necessary (e.g., in parameterized tests where some stubs might not be hit in all cases).
❌ BAD: Silencing all strictness with @MockitoSettings(strictness = Strictness.LENIENT)
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class MyServiceTest {
@Test
void someTest() {
when(mockDependency.someMethod()).thenReturn("value");
}
}
✅ GOOD: Remove unused stubs or use lenient() for specific stubs
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Test
void someTest() {
Mockito.lenient().when(mockDependency.optionalMethod()).thenReturn("optional");
}
}
5. Use ArgumentCaptor for Complex Argument Verification
When you need to inspect the exact values of objects passed to a mock, ArgumentCaptor is your tool.
❌ BAD: Generic any() when specific content matters
verify(invoiceRepository).save(any(Invoice.class));
✅ GOOD: Capture and assert on the actual object's state
import org.mockito.ArgumentCaptor;
@Test
void shouldSaveInvoiceWithCorrectDetails() {
ArgumentCaptor<Invoice> captor = ArgumentCaptor.forClass(Invoice.class);
verify(invoiceRepository).save(captor.capture());
Invoice savedInvoice = captor.getValue();
assertEquals("ORD-123", savedInvoice.getOrderId());
assertEquals(BigDecimal.valueOf(150.00), savedInvoice.getAmount());
}
6. Prefer Mocks over Spies
Use mocks for external, unmanaged dependencies where you control all behavior. Use spies only when you need partial real behavior of an object.
❌ BAD: Spying on simple dependencies that should be fully mocked
@Spy
UserService userService = new UserService(userRepository);
✅ GOOD: Mocking dependencies, using @InjectMocks for SUT
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
7. Use Parameterized Tests for Similar Scenarios
Avoid duplicate test methods for different inputs. Parameterized tests keep your test suite DRY and maintainable.
❌ BAD: Repetitive tests for different inputs
@Test void shouldReturnBronzeFor0Points() { }
@Test void shouldReturnSilverFor1000Points() { }
✅ GOOD: Single parameterized test
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class LoyaltyServiceTest {
@InjectMocks
LoyaltyService loyaltyService;
@ParameterizedTest
@CsvSource({
"0, BRONZE",
"1000, SILVER",
"5000, GOLD"
})
void shouldReturnCorrectTierForPoints(int points, String expectedTier) {
String actualTier = loyaltyService.calculateTier(points);
assertEquals(expectedTier, actualTier);
}
}
8. Mock Final Classes, Static Methods, and Constructors (with Caution)
Mockito 5 (and newer) supports mocking these without extra dependencies. Use this feature judiciously for testing legacy code or third-party APIs that were not designed for testability. Avoid it for your own code; design for testability instead.
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import org.mockito.MockedStatic;
@Test
void shouldMockStaticMethod() {
try (MockedStatic<MyStaticUtil> mockedStatic = mockStatic(MyStaticUtil.class)) {
when(MyStaticUtil.getConstantValue()).thenReturn("MOCKED_VALUE");
assertEquals("MOCKED_VALUE", MyStaticUtil.getConstantValue());
}
}