Keywords: Mockito | Argument Matchers | Java Testing
Abstract: This article delves into the design principles, implementation mechanisms, and common issues of Mockito argument matchers. By analyzing core concepts such as static method calls, argument matcher stack storage, and thread-safe implementation, it explains why Mockito matchers require all arguments to use matchers uniformly and why typical behaviors like InvalidUseOfMatchersException occur. The paper contrasts the fundamental differences between Mockito matchers and Hamcrest matchers, provides practical code examples illustrating the importance of matcher invocation order, and offers debugging and troubleshooting advice.
Basic Concepts of Mockito Argument Matchers
Mockito argument matchers are a set of static methods that serve as placeholders for arguments during calls to when and verify. Unlike Hamcrest matchers, Mockito matchers return type T rather than Matcher<T> objects, allowing matching expressions to be embedded directly into method invocations. For example:
verify(foo).setPowerLevel(gt(9000));
assertThat(foo.getPowerLevel(), is(greaterThan(9000))); // Hamcrest-style
Mockito matchers are invoked through static methods in org.mockito.Matchers and org.mockito.AdditionalMatchers, such as eq, any, and gt. In Mockito 1.x, ArgumentMatcher<T> extended org.hamcrest.Matcher<T>; in Mockito 2.0+, Mockito no longer directly depends on Hamcrest, and ArgumentMatcher<T> does not implement the Hamcrest interface, though adapters like argThat remain available via MockitoHamcrest.
How Matchers Operate
When argument matchers are not used, Mockito records argument values and compares them via the equals method:
when(foo.quux(3, 5)).thenReturn(true);
when(foo.quux(eq(3), eq(5))).thenReturn(true); // Equivalent
Using matchers like anyInt() or gt(5), Mockito stores matcher objects, skipping equality checks and applying custom matching logic. argumentCaptor.capture() stores a matcher that captures arguments. Matchers return dummy values such as zero, empty collections, or null. Due to type erasure, any() or argThat(...) may return null, potentially causing NullPointerException if auto-unboxing is attempted.
Implementation Details and Internal Structure
Matchers are stored as Hamcrest-style objects in a stack within ArgumentMatcherStorage. MockitoCore and Matchers each own a ThreadSafeMockingProgress instance containing a ThreadLocal that holds MockingProgress instances. Thus, mock and matcher state is static but consistent within thread scope.
Matcher calls typically only add to the stack, with exceptions for combinatory matchers like and, or, and not. This relies on Java's left-to-right argument evaluation order:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
- Add
anyInt()to the stack. - Add
gt(10)to the stack. - Add
lt(20)to the stack. - Remove
gt(10)andlt(20), addand(gt(10), lt(20)). - Call
foo.quux(0, 0), returning the default valuefalse, with Mockito markingquux(int, int)as the most recent call. - Call
when(false), preparing to stub the method; with two matchers on the stack, Mockito stubs withany()for the first argument andand(gt(10), lt(20))for the second, then clears the stack.
This reveals key rules: if one matcher is used, all arguments must be matched, as Mockito cannot distinguish between quux(anyInt(), 0) and quux(0, anyInt()); invocation order is critical, extracting matchers to variables may alter order and cause errors, while extracting to methods works:
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack in reverse order
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// Helper method invokes matchers in correct order
Common Issues and Debugging Strategies
InvalidUseOfMatchersException: Ensure each argument has exactly one matcher call (if matchers are used), and matchers are not used outside when or verify. Avoid calling mock methods while providing matcher arguments, or attempting to stub/verify final methods with matchers.
NullPointerException: (Integer) any() returns null, while any(Integer.class) returns 0; expecting an int instead of Integer may cause exceptions. Prefer anyInt() to avoid auto-boxing issues. Additionally, when(foo.bar(any())).thenReturn(baz) actually calls foo.bar(null); if stubbed to throw an exception, use doReturn(baz).when(foo).bar(any()) to skip stubbed behavior.
For debugging, use MockitoJUnitRunner or call validateMockitoUsage() in tearDown/@After methods to check stack state. Adding validateMockitoUsage calls directly helps identify matcher misuse.