Mockito is one of the most popular Java mocking frameworks, widely adopted in enterprise and open-source projects. While getting started with Mockito is easy, writing maintainable, scalable, and production-ready tests requires discipline and following best practices.
This guide explores best practices for using Mockito in real-world Java projects, from writing clean unit tests to integrating with CI/CD pipelines.
Why Mockito Best Practices Matter
- Prevent brittle tests that break with minor code changes.
- Ensure maintainability in large test suites.
- Improve readability for teams working on shared codebases.
- Support CI/CD pipelines with reliable, deterministic tests.
Think of Mockito as a Swiss Army knife — powerful, but only if you use the right tool for the right job.
Best Practice 1: Prefer Constructor Injection
Mockito works best when classes use constructor injection for dependencies.
class OrderService {
private final PaymentGateway paymentGateway;
OrderService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
boolean placeOrder(String item) {
return paymentGateway.process(item);
}
}
Constructor injection makes it easy to inject mocks using @InjectMocks
and avoids issues with hidden dependencies.
Best Practice 2: Use @ExtendWith(MockitoExtension.class)
Always initialize mocks with the JUnit 5 extension:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
PaymentGateway paymentGateway;
@InjectMocks
OrderService orderService;
@Test
void shouldProcessOrder() {
when(paymentGateway.process("Book")).thenReturn(true);
assertTrue(orderService.placeOrder("Book"));
}
}
This ensures fresh mocks for each test run.
Best Practice 3: Verify Behavior, Not Implementation
Bad test (too coupled to internals):
verify(paymentGateway, times(1)).process("Book");
Better test (focus on outcome):
assertTrue(orderService.placeOrder("Book"));
Verification is powerful but overusing it creates fragile tests.
Best Practice 4: Keep Tests Focused
- Test one behavior per test.
- Avoid mixing unrelated verifications.
- Use descriptive test names like
shouldSendEmailOnRegistration
.
Focused tests are easier to read and maintain.
Best Practice 5: Avoid Over-Stubbing
Don’t stub methods that are never used.
❌ Bad:
when(paymentGateway.process("Pen")).thenReturn(false);
✅ Good:
when(paymentGateway.process("Book")).thenReturn(true);
Unnecessary stubbing adds noise.
Best Practice 6: Use Argument Matchers Wisely
Mockito provides any()
, eq()
, and argThat()
. Avoid mixing matchers with raw values.
verify(paymentGateway).process(eq("Book"));
This improves readability and avoids errors.
Best Practice 7: Reset Mocks Sparingly
Prefer fresh mocks per test instead of resetting:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway paymentGateway;
@InjectMocks OrderService orderService;
}
reset()
is usually a code smell — use clearInvocations()
if needed.
Best Practice 8: Use Spies for Legacy Code
Spies allow partial mocking when working with legacy systems.
List<String> list = spy(new ArrayList<>());
list.add("Hello");
verify(list).add("Hello");
But prefer refactoring over heavy spying.
Best Practice 9: Combine Mockito with Testcontainers
Use Mockito for unit tests and Testcontainers for integration tests:
@Test
void shouldStartPostgres() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
assertTrue(postgres.isRunning());
}
}
Together, they cover both mocked dependencies and real-world systems.
Best Practice 10: Integrate with CI/CD
- Run unit tests with mocks on every commit.
- Run Testcontainers-based integration tests nightly or in staging.
- Collect code coverage reports with JaCoCo.
This ensures speed + reliability.
Version Tracker
- JUnit 4 → JUnit 5: Migration to
@ExtendWith(MockitoExtension.class)
. - Mockito Updates: Static and final method mocking support.
- Testcontainers Growth: Wider support for cloud-native dependencies.
Conclusion & Key Takeaways
Mockito is a powerful framework, but misuse leads to fragile tests. Following best practices ensures tests are robust, readable, and production-ready.
Key Takeaways:
- Prefer constructor injection.
- Keep tests focused and outcomes-driven.
- Use mocks, spies, and matchers responsibly.
- Avoid over-stubbing and unnecessary resets.
- Combine with Testcontainers for full coverage.
FAQ
1. Should I verify every method call?
No, focus on outcomes, not implementation details.
2. Is using reset() in Mockito good practice?
Usually no — prefer fresh mocks per test.
3. Can I mock static methods in Mockito?
Yes, since Mockito 3.4+ using mockStatic()
.
4. How does @InjectMocks work?
It injects mocks into the class under test via constructors, setters, or fields.
5. Should I use spies often?
No, they’re best for legacy code where refactoring is hard.
6. Can Mockito and Testcontainers be used together?
Yes — Mockito for isolation, Testcontainers for real-world validation.
7. How do I avoid flaky tests?
Avoid over-stubbing and reset; keep tests independent.
8. Is Mockito enough for microservices?
Use Mockito for unit tests; pair with contract tests and Testcontainers.
9. How do I measure test coverage?
Use JaCoCo with JUnit 5 and Mockito.
10. Can I use Mockito in CI/CD pipelines?
Absolutely — it integrates seamlessly with Maven, Gradle, and Jenkins/GitHub Actions.