Common Mockito Pitfalls
Mockito is one of the most widely used mocking frameworks in the Java ecosystem. It promises quick isolation, clean test doubles, and less coupling between tests and infrastructure. For many teams, that promise is real. But the framework is also flexible enough to let developers shoot themselves in the foot. The gap between “easy to use” and “easy to misuse” is where a lot of frustrating test problems begin.
A very common Mockito pitfall appears during stubbing. Argument matchers such as anyString(), anyInt(), or any(User.class) seem convenient, and they often solve a compiler problem instantly. The trouble starts when they become a habit rather than a deliberate choice.
The trap: broad matchers replace concrete argument values. The test then stops checking what the system actually passed.
// Fragile: the test cannot tell "Alice" from "Bob"
when(userRepository.findByName(anyString())).thenReturn(new User("Default"));
The fix: use concrete values whenever possible. If the exact value matters, stub with that value. If it does not, use a matcher, but do so intentionally.
when(userRepository.findByName("Alice")).thenReturn(new User("Alice"));
Another frequent mistake is mixing raw values and matchers in the same method call. Mockito expects consistency. If you use a matcher, every argument in that call should follow the matcher pattern.
// Wrong: mixing a matcher with a raw value
when(service.process(anyString(), 123)).thenReturn(true);
// Right: wrap the literal with eq()
when(service.process(anyString(), eq(123))).thenReturn(true);
Confusing Mocks and Spies
Mockito provides two main kinds of test doubles: Mocks and Spies. Their defaults are not the same, and confusing them leads to subtle bugs.
A Mock does not execute real logic by default. Its methods return default values for the declared return type, such as null, 0, or false.
A Spy, by contrast, keeps the real behavior of an object and only overrides the methods you choose.
That difference creates a classic trap. Developers often try to stub a spy using when(...).thenReturn(...).
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
// Dangerous: this actually calls spyList.get(0)
when(spyList.get(0)).thenReturn("foo");
If the underlying list is empty, the real method runs and throws before the stub is applied.
The safer syntax uses doReturn():
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
doReturn("foo").when(spyList).get(0);
Understanding this distinction prevents unexpected exceptions and keeps spy tests predictable.
Ignoring Void Methods
void methods require special handling in Mockito. Because they do not return a value, the usual when(mock.method()).thenReturn(...) pattern does not apply.
For a void method, you use dedicated syntax.
To make a void method throw an exception:
doThrow(new RuntimeException("Something went wrong"))
.when(mock)
.save();
To verify that a void method was called:
verify(mock, times(1)).save();
This behavior is simple once you know it, but forgetting the special syntax is a real source of compile errors and confusing test failures.
Missing or Overly Strict Verification
Mockito tests are not only about stubbing return values. Verification is equally important. A test can return the correct result while still failing to confirm that the right interactions occurred.
Missing verification often means the test checks only the outcome, not whether the dependency was used correctly.
Overly strict verification can make tests brittle. Verifying every internal call or enforcing an exact order may cause failures after harmless refactors.
A better approach is to verify the interactions that carry business meaning. For example, confirm that a repository save method was called, or that a payment client was triggered. Avoid validating every small implementation detail.
The verifyNoMoreInteractions() method can also become dangerous if used too broadly. It may create maintenance overhead when new logging or instrumentation calls appear.
Key Takeaways
Mockito is powerful, but power brings responsibility. The most useful habits include:
- Keep stubs precise.
- Avoid broad
any()matchers unless arguments truly do not matter. - Use concrete values or
eq()for exact matching. - Do not mix raw values and matchers.
- Understand the difference between Mocks and Spies.
- Use
doReturn().when()for spies. - Use
doThrow()andverify()forvoidmethods. - Verify behavior selectively, not exhaustively.
By avoiding these common traps, developers can write unit tests that are more reliable, easier to understand, and faster to maintain. Mockito works best when the team understands not just the syntax, but the reasoning behind it.