Mockito and JUnit 6 are the most powerful duo in the Java testing toolkit. Mockito handles mock creation, stubbing, and verification while JUnit 6 orchestrates the test lifecycle. Together they let you test any unit in complete isolation, regardless of how many dependencies it has. This guide covers every Mockito feature you need β from basic mocking to advanced argument captors, spies, and common pitfalls to avoid.
Setup: Mockito with JUnit 6
<!-- Mockito Core + JUnit 5/6 integration -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
<!-- Note: spring-boot-starter-test already includes mockito-junit-jupiter -->
Activate Mockitoβs annotation processing by adding @ExtendWith(MockitoExtension.class) to your test class:
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) // enables @Mock, @InjectMocks, @Captor, @Spy
class PaymentServiceTest { }
Creating Mocks: @Mock vs Mockito.mock()
@ExtendWith(MockitoExtension.class)
class MockCreationTest {
// Annotation style β cleanest, preferred for fields
@Mock
private PaymentGateway paymentGateway;
@Mock
private NotificationService notificationService;
// Programmatic style β useful when you need a mock inside a method
@Test
void programmaticMockCreation() {
// Create a mock inside the test method
UserRepository mockRepo = Mockito.mock(UserRepository.class);
when(mockRepo.findById(1L)).thenReturn(Optional.of(new User(1L, "Alice")));
User found = mockRepo.findById(1L).orElseThrow();
assertEquals("Alice", found.getName());
}
}
Stubbing with when().thenReturn()
@ExtendWith(MockitoExtension.class)
class StubbingExamplesTest {
@Mock private PaymentGateway paymentGateway;
@InjectMocks private PaymentService paymentService;
@Test
@DisplayName("Successful payment returns a confirmation number")
void successfulPaymentReturnsConfirmationNumber() {
// Stub: when gateway.charge() is called with ANY double, return a confirmation
when(paymentGateway.charge(anyDouble()))
.thenReturn(new PaymentResult("CONF-12345", true));
PaymentResult result = paymentService.processPayment(99.99);
assertTrue(result.isSuccessful());
assertEquals("CONF-12345", result.getConfirmationNumber());
}
@Test
@DisplayName("Gateway failure propagates as PaymentException")
void gatewayFailureThrowsPaymentException() {
// Stub: throw an exception when the gateway is called
when(paymentGateway.charge(anyDouble()))
.thenThrow(new GatewayTimeoutException("Gateway timed out"));
assertThrows(PaymentException.class,
() -> paymentService.processPayment(99.99));
}
@Test
@DisplayName("Multiple calls can return different values")
void consecutiveCallsReturnDifferentValues() {
// First call returns PENDING, second returns COMPLETED
when(paymentGateway.checkStatus(anyString()))
.thenReturn(PaymentStatus.PENDING)
.thenReturn(PaymentStatus.COMPLETED);
assertEquals(PaymentStatus.PENDING, paymentGateway.checkStatus("TX-001"));
assertEquals(PaymentStatus.COMPLETED, paymentGateway.checkStatus("TX-001"));
}
}