Let's get started with a Microservice Architecture with Spring Cloud:
Why We Should Not Mock Collections With Mockito
Last updated: March 18, 2026
1. Overview
Mockito is one of the most popular testing libraries in the Java ecosystem. We use it to isolate units of code, replace external dependencies, and write fast and focused tests. However, Mockito is often overused, and one of the most common misuses is mocking Java collections such as List, Set, or Map.
At first glance, mocking collections feels harmless. Mockito allows it, the tests compile, and everything looks fine. Over time, though, this practice leads to brittle tests, unrealistic behavior, and reduced confidence. On newer Java versions, it can even cause test failures.
In this tutorial, we’ll explore why mocking collections is problematic and what we should do instead.
2. Collections Are Not Our Dependencies
Before mocking anything, we should decide whether it is truly a dependency. Dependencies are usually external, slow, or non-deterministic, such as databases, REST clients, message brokers, or system clocks. Java collections do not fall into this category. They are part of the core JDK, deterministic, lightweight, and extensively tested.
When we mock a collection, we are not isolating our code from external behavior. Instead, we are replacing well-defined Java behavior with artificial stubs. This increases complexity without providing meaningful isolation.
3. Example Code
Let’s start with a simple service class that uses a collection internally:
public class UserService {
private final List<String> users;
public UserService(List<String> users) {
this.users = users;
}
public boolean hasUsers() {
return !users.isEmpty();
}
public String getFirstUser() {
if (users.isEmpty()) {
return null;
}
return users.get(0);
}
}
The List here is purely a data structure. It does not represent an external collaborator or a side-effecting dependency.
4. Mocking a Collection
A real collection maintains an internal state. When we add an element, its size changes automatically. A mocked collection has no such behavior unless we explicitly define it. This difference often leads to tests that pass even though they do not reflect real runtime behavior.
The situation becomes even more problematic on newer Java versions.
The following test intentionally demonstrates the problems with mocking collections. On modern Java versions, this test may fail before it even runs:
@Test
void shouldFailToMockCollection_onModernJavaVersions() {
// This line may fail on Java 21+ due to JVM restrictions
List<String> users = mock(List.class);
UserService userService = new UserService(users);
// The test may never reach this point
userService.hasUsers();
}
On Java 21 and later, Mockito may throw an exception similar to:
Mockito cannot mock this class: interface java.util.List
Could not modify all classes [Iterable, SequencedCollection, Collection, List]
This failure is not caused by incorrect test logic. It is the result of stricter JVM rules around runtime instrumentation of core JDK types.
5. Tests Become Over-Specified and Fragile
Even when mocking collections is technically possible, tests tend to become tightly coupled to implementation details. They often verify how many times isEmpty() or get(0) is called rather than focusing on observable behavior. Small internal refactorings can break tests even when functionality remains unchanged.
Good unit tests should protect refactoring, not resist it.
6. Using a Real Collection Instead
Using a real collection avoids all of these problems:
@Test
void givenList_whenRealCollectionIsUsed_thenShouldReturnFirstUser() {
List<String> users = new ArrayList<>();
users.add("Joey");
UserService userService = new UserService(users);
assertTrue(userService.hasUsers());
assertEquals("Joey", userService.getFirstUser());
}
This test works consistently across Java versions, requires no stubbing, and accurately mirrors production behavior.
Edge cases are also easier to test with real collections:
@Test
void givenEmptyList_whenUserListIsEmpty_thenShouldReturnNull() {
List<String> users = new ArrayList<>();
UserService userService = new UserService(users);
assertNull(userService.getFirstUser());
}
This test clearly documents the expected behavior without relying on Mockito.
The need to mock collections often signals deeper design problems, such as overly coupled logic or unclear responsibilities. Using real collections in tests tends to expose these issues and encourages cleaner APIs and better separation of concerns.
7. Conclusion
In this article, we saw that mocking Java collections is rarely a good idea. It leads to brittle tests, unrealistic behavior, and unnecessary coupling to implementation details. On modern Java versions, it can even result in runtime failures due to stricter JVM constraints.
Mockito remains a powerful and valuable tool when used correctly. It should mock true collaborators and external dependencies, not core data structures. By using real collections in tests, we write code that is clearer, safer, and more resilient to change.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.















