Unit testing is about testing code in isolation. But most Java classes depend on other classes or external services. Without isolation, tests can become slow, flaky, and difficult to maintain. This is where Mockito comes in.
Mockito is the most widely used Java mocking framework. It allows developers to create mock objects, define their behavior, and verify interactions. Combined with JUnit 5, Mockito provides a powerful foundation for writing reliable unit tests.
Why Mockito?
- Isolate Dependencies: Replace real services with test doubles.
- Control Behavior: Define return values and simulate errors.
- Verify Interactions: Ensure methods were called as expected.
- Boost Speed: Avoid network/database delays.
- CI/CD Friendly: Reliable tests that work in pipelines.
Think of Mockito as a stunt double in a movie — it performs the risky work so the main actor (your class under test) can shine.
Adding Mockito to Your Project
Maven Setup
<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 Setup
testImplementation 'org.mockito:mockito-core:5.7.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.7.0'
First Mock Example
Let’s imagine an OrderService that depends on a PaymentGateway.
class PaymentGateway {
boolean process(double amount) {
// Connects to external system
return true;
}
}
class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) {
this.gateway = gateway;
}
boolean placeOrder(double amount) {
return gateway.process(amount);
}
}
Writing a Test with Mockito
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class OrderServiceTest {
@Test
void shouldPlaceOrderSuccessfully() {
// Create a mock
PaymentGateway gateway = mock(PaymentGateway.class);
// Define behavior
when(gateway.process(100.0)).thenReturn(true);
// Inject mock into service
OrderService service = new OrderService(gateway);
// Execute method
boolean result = service.placeOrder(100.0);
// Verify result and interaction
assertTrue(result);
verify(gateway).process(100.0);
}
}
Here’s what happened:
- Created a mock of
PaymentGateway
. - Stubbed the method
process
to returntrue
. - Injected the mock into
OrderService
. - Verified the call happened with the expected argument.
Annotations for Cleaner Tests
Mockito provides annotations like @Mock
and @InjectMocks
.
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(MockitoExtension.class)
class OrderServiceWithAnnotationsTest {
@Mock
PaymentGateway gateway;
@InjectMocks
OrderService service;
@Test
void shouldPlaceOrderSuccessfully() {
when(gateway.process(200.0)).thenReturn(true);
assertTrue(service.placeOrder(200.0));
}
}
This reduces boilerplate and improves readability.
Advanced Mockito Features
-
Argument Matchers:
when(gateway.process(anyDouble())).thenReturn(true);
-
Throwing Exceptions:
when(gateway.process(anyDouble())).thenThrow(new RuntimeException("Service down"));
-
Spies: Partial mocks that wrap real objects.
List<String> spyList = spy(new ArrayList<>()); spyList.add("Hello"); verify(spyList).add("Hello");
Mockito with Testcontainers
Mockito is for unit tests, but integration tests require real systems. You can combine Mockito and Testcontainers in the same project:
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
class DatabaseIntegrationTest {
@Test
void shouldStartDatabase() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
assert postgres.isRunning();
}
}
}
Use mocks for unit tests and Testcontainers for integration tests to cover both speed and realism.
Best Practices
- Mock only external dependencies, not the class under test.
- Avoid overusing mocks — too many mocks make tests fragile.
- Use annotations (
@Mock
,@InjectMocks
) for cleaner tests. - Verify behavior only when necessary.
- Combine mocks and real integration tests for balance.
Version Tracker
- JUnit 4 → JUnit 5: Mockito integrates seamlessly with Jupiter extensions.
- Mockito Updates: Now supports mocking static, final, and constructor methods.
- Testcontainers Growth: Allows integration alongside unit-level mocking.
Conclusion & Key Takeaways
Mockito makes unit testing faster, safer, and easier by simulating dependencies. With JUnit 5 integration and annotation support, developers can write clean, reliable, and maintainable tests.
Key Takeaways:
- Use
mock()
to create test doubles. - Stub methods with
when().thenReturn()
. - Verify interactions with
verify()
. - Prefer annotations for cleaner code.
- Balance unit mocks with integration testing via Testcontainers.
FAQ
1. What’s the difference between unit and integration tests?
Unit tests isolate code with mocks; integration tests use real systems.
2. How do I mock a static method in Mockito?
Use mockStatic()
(Mockito 3.4+).
3. Can Mockito simulate exceptions?
Yes, with thenThrow()
in stubs.
4. What’s the difference between a mock and a spy?
Mocks replace objects completely; spies wrap real ones.
5. Should I mock private methods?
No, private methods should be tested indirectly.
6. Do mocks improve test performance?
Yes, by avoiding slow I/O operations.
7. Can I use Mockito in CI/CD pipelines?
Yes, Mockito is lightweight and pipeline-friendly.
8. How do I avoid fragile mock setups?
Mock only external collaborators, not every method.
9. Does Mockito work with Testcontainers?
Yes, for projects needing both unit and integration coverage.
10. Should I migrate from JUnit 4 to JUnit 5 for Mockito?
Yes, JUnit 5 provides modern features and better extensions.