Mockito: A Practical Guide for Java Unit TestingUnit testing is the foundation of reliable, maintainable Java applications. Mockito is one of the most popular mocking frameworks in the Java ecosystem, designed to help developers isolate units of code by creating lightweight, controllable mock objects. This guide covers practical usage, patterns, and best practices to make your tests clearer, faster, and less brittle.
What is Mockito?
Mockito is a Java mocking framework that enables you to create mock implementations of classes and interfaces for use in unit tests. Instead of instantiating real collaborators (which may be slow, have side effects, or be hard to configure), you create mocks that return predefined responses and verify interactions.
Why use Mockito?
- Isolation: Test a class without involving its real dependencies (databases, web services, file systems).
- Control: Precisely define how dependencies behave in different test scenarios.
- Verification: Assert that methods were called with expected parameters and frequency.
- Simplicity: Clean, fluent API designed for readability and maintainability.
Core Concepts and API
- Mock creation: mock(), @Mock
- Behavior stubbing: when(…).thenReturn(…), thenThrow(…)
- Verification: verify(), times(), never(), atLeastOnce()
- Argument matchers: any(), eq(), argThat()
- Spies: spy() to wrap real objects while allowing selective stubbing
- ArgumentCaptor: capture arguments passed to mocks for assertions
- doReturn/doThrow style: for stubbing void methods or spies
Setup and Dependencies
Add Mockito (and JUnit) to your build tool. Example with Maven:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency>
(Adjust the version to the latest stable release.)
For JUnit 5 integration, include mockito-junit-jupiter:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency>
Basic Example
A simple example testing a service that depends on a repository:
public class UserService { private final UserRepository repo; public UserService(UserRepository repo) { this.repo = repo; } public String getDisplayName(int id) { User user = repo.findById(id); return user == null ? "Unknown" : user.getFirstName() + " " + user.getLastName(); } }
Test with Mockito + JUnit 5:
import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.Mock; @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock UserRepository repo; @Test void returnsDisplayNameWhenUserExists() { when(repo.findById(1)).thenReturn(new User("Jane","Doe")); UserService svc = new UserService(repo); String name = svc.getDisplayName(1); assertEquals("Jane Doe", name); verify(repo).findById(1); } }
Stubbing vs. Verification
- Stubbing specifies what a mock should return when called (arrange).
- Verification checks that interactions occurred as expected (assert).
Use stubbing for flows that need controlled returns; use verification to ensure methods were invoked or to assert side effects.
Argument Matchers
Argument matchers let you be flexible about parameter values:
when(repo.findById(anyInt())).thenReturn(new User("Generic","User")); verify(repo).save(argThat(user -> user.getEmail().contains("@")));
Rules: either use matchers for all arguments in a call, or none; mixing raw values and matchers leads to errors.
Spies
Use spies when you want to call real methods but still stub or verify some interactions:
List<String> list = new ArrayList<>(); List<String> spyList = spy(list); spyList.add("one"); when(spyList.size()).thenReturn(100); // prefer doReturn for spies to avoid side effects
Prefer doReturn/doThrow for stubbing spies to avoid executing real methods during stubbing:
doReturn(100).when(spyList).size();
Handling Void Methods and Exceptions
For void methods or when you need to throw exceptions:
doThrow(new RuntimeException("fail")).when(repo).delete(anyInt());
For stubbing chained or fluent APIs, carefully stub intermediate calls or use Answer for custom behavior.
ArgumentCaptor
Capture arguments passed to mocks for deeper assertions:
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class); verify(repo).save(captor.capture()); User saved = captor.getValue(); assertEquals("[email protected]", saved.getEmail());
Advanced: Custom Answers
Use Answer to compute responses dynamically based on invocation:
when(repo.findById(anyInt())).thenAnswer(invocation -> { int id = invocation.getArgument(0); return new User("User"+id, "Test"); });
This is useful for parameterized behavior or simulating stateful collaborators.
Testing Asynchronous or Multi-threaded Code
Mockito is synchronous by design. For async flows:
- Avoid mocking threads; prefer injecting schedulers or executors.
- Use CompletableFuture and supply completed futures in stubs.
- For verifying later interactions, use Awaitility or polling assertions.
Best Practices
- Mock behavior, not implementation: prefer mocking interfaces or small collaborator contracts.
- Keep tests focused: one behavior per test.
- Avoid over-mocking: if you mock too many things, tests become brittle and mimic implementation.
- Use @ExtendWith(MockitoExtension.class) to initialize @Mock/@InjectMocks cleanly.
- Prefer constructor injection in code to make dependencies testable.
- Use real objects for value types (POJOs) and mocks for external systems.
- Favor verifyZeroInteractions/verifyNoMoreInteractions sparingly; they can make tests fragile.
- Use doReturn for spies and for stubbing methods that would execute real logic during stubbing.
Common Pitfalls
- Mixing raw values and matchers in the same call.
- Forgetting to initialize mocks (missing MockitoExtension or MockitoAnnotations.openMocks).
- Stubbing methods on null references or final classes (mockito-inline required for final classes/methods).
- Excessive use of spies leading to tests dependent on real implementations.
- Relying on ordering of interactions unless explicitly testing sequences (use InOrder).
Mockito with Spring Boot
Spring Boot tests often combine Mockito with @WebMvcTest or @SpringBootTest. Use @MockBean to replace beans in the application context:
@MockBean private UserRepository repo;
This keeps Spring context lightweight while letting you stub repository behavior.
Migration Tips (older Mockito versions)
If upgrading from Mockito 1/2/3 to newer versions:
- API is mostly stable, but consider switching from MockitoAnnotations.initMocks to MockitoAnnotations.openMocks.
- For final classes/methods, add mockito-inline or enable the mock-maker-inline extension.
- Check deprecated APIs and prefer newer fluent style.
Example: Testing a Controller-Service-Repository Flow
- Stub repository to return data.
- Verify service logic transforms data.
- Mock external client to simulate failures and assert controller handles errors gracefully.
Concrete testing pyramid: unit tests (Mockito) at the base, lightweight integration tests (slice tests) above, full end-to-end tests at the top.
Quick Reference Cheat Sheet
- Create mock: mock(MyClass.class) or @Mock
- Stub: when(mock.method(…)).thenReturn(value)
- Verify: verify(mock).method(…)
- Matchers: any(), eq(), argThat(…)
- Capture: ArgumentCaptor.forClass(…)
- Spy: spy(realObject); doReturn(x).when(spy).method()
- Throw: doThrow(new Exception()).when(mock).voidMethod()
Conclusion
Mockito helps you write focused, fast unit tests by letting you control and verify interactions with dependencies. Use it to isolate units, simulate edge cases, and keep tests deterministic. Balance mocking with the use of real, simple objects to avoid brittle tests, and follow dependency injection and clean-test practices to maximize the benefits.
Leave a Reply