Modern Java applications often depend on external services, databases, or APIs. Testing such code directly is slow, unreliable, and hard to maintain. Mockito, the most popular Java mocking framework, solves this problem by allowing developers to create mock objects that simulate real dependencies.
In this tutorial, we’ll dive into the four most important building blocks of Mockito:
mock()
when()
thenReturn()
verify()
By the end, you’ll understand how to isolate your code, control behavior, and verify interactions for reliable and maintainable unit tests.
Why Learn Mockito Basics?
- Isolate business logic from external dependencies.
- Control outputs for predictable test results.
- Simulate errors without breaking real systems.
- Verify interactions with dependencies.
- Boost test speed and reliability.
Think of mocks as stand-ins in a play: they perform roles so the main actor (your class under test) can focus on the script.
Setting Up Mockito with JUnit 5
Maven Dependencies
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
Gradle Dependencies
testImplementation 'org.mockito:mockito-core:5.7.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.7.0'
Example: Order Service with Dependency
class PaymentGateway {
boolean process(double amount) {
// Calls external payment provider
return true;
}
}
class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) {
this.gateway = gateway;
}
boolean placeOrder(double amount) {
return gateway.process(amount);
}
}
Using mock(), when(), thenReturn(), verify()
Step 1: Create a Mock
PaymentGateway gateway = mock(PaymentGateway.class);
This creates a mock object of PaymentGateway
.
Step 2: Define Behavior with when() and thenReturn()
when(gateway.process(100.0)).thenReturn(true);
This stubs the method so that when process(100.0)
is called, it returns true
.
Step 3: Inject Mock into Service
OrderService service = new OrderService(gateway);
boolean result = service.placeOrder(100.0);
The service now depends on the mocked gateway.
Step 4: Verify Interactions
verify(gateway).process(100.0);
This ensures the method process()
was called with the expected argument.
Complete Example
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class OrderServiceTest {
@Test
void shouldPlaceOrderSuccessfully() {
PaymentGateway gateway = mock(PaymentGateway.class);
when(gateway.process(100.0)).thenReturn(true);
OrderService service = new OrderService(gateway);
boolean result = service.placeOrder(100.0);
assertTrue(result);
verify(gateway).process(100.0);
}
}
Advanced Mockito Usage
Argument Matchers
when(gateway.process(anyDouble())).thenReturn(true);
Throwing Exceptions
when(gateway.process(anyDouble())).thenThrow(new RuntimeException("Service down"));
Verifying Call Counts
verify(gateway, times(2)).process(100.0);
Using Annotations
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
PaymentGateway gateway;
@InjectMocks
OrderService service;
@Test
void shouldPlaceOrderSuccessfully() {
when(gateway.process(200.0)).thenReturn(true);
assertTrue(service.placeOrder(200.0));
verify(gateway).process(200.0);
}
}
Mockito vs Testcontainers
Mockito is for unit tests, while Testcontainers is for integration tests.
- Use Mockito mocks to isolate logic.
- Use Testcontainers to validate against real databases or message brokers.
Example with Testcontainers:
@Test
void shouldStartDatabase() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
assert postgres.isRunning();
}
}
Best Practices
- Mock only external dependencies, not the class under test.
- Keep mocks simple and avoid over-mocking.
- Verify only meaningful interactions.
- Combine unit mocks with integration tests for confidence.
- Use annotations (
@Mock
,@InjectMocks
) for cleaner code.
Version Tracker
- JUnit 4 → JUnit 5: Better integration with Mockito via extensions.
- Mockito Updates: Support for mocking static, final, and constructor methods.
- Testcontainers Growth: Wider ecosystem for integration testing alongside mocks.
Conclusion & Key Takeaways
Mockito provides a simple yet powerful toolkit for creating mocks, defining behavior, and verifying interactions. With mock()
, when()
, thenReturn()
, and verify()
, you can write fast, reliable, and isolated unit tests.
Key Takeaways:
- Use
mock()
to create test doubles. - Define behavior with
when().thenReturn()
. - Validate interactions with
verify()
. - Prefer annotations for maintainable test suites.
- Balance unit mocks with integration tests.
FAQ
1. What’s the difference between unit and integration tests?
Unit tests isolate logic with mocks; integration tests use real systems.
2. How do I mock a static method in Mockito?
Use mockStatic()
introduced in Mockito 3.4+.
3. Can Mockito simulate exceptions?
Yes, use thenThrow()
.
4. What’s the difference between a mock and a spy?
Mocks replace objects completely, while spies wrap real ones.
5. Do I need 100% coverage with mocks?
No, focus on meaningful coverage.
6. Should I mock private methods?
No, private methods should be tested indirectly.
7. Can I use Mockito with JUnit 5?
Yes, via mockito-junit-jupiter
extension.
8. How do I avoid overusing mocks?
Mock only external dependencies, not everything.
9. Does Mockito work with Testcontainers?
Yes, they complement each other for unit + integration testing.
10. Should I migrate from JUnit 4 to JUnit 5?
Yes, JUnit 5 offers modern features and better Mockito integration.