Keywords: Mockito | Unit Testing | Method Mocking
Abstract: This article explores how to configure mocked methods in Mockito to return different results on subsequent invocations. Through detailed analysis of thenReturn chaining and thenAnswer custom logic, combined with ExecutorCompletionService testing scenarios, it demonstrates effective simulation of non-deterministic responses. The article includes comprehensive code examples and best practice recommendations to help developers write more robust concurrent test code.
Introduction
In unit testing, mocking method return values is a common requirement. When testing scenarios where multiple calls to the same method should return different results, Mockito provides several flexible solutions. This is particularly important when testing concurrent components like ExecutorCompletionService, where task completion order may be non-deterministic, but business logic must ensure consistent final outcomes.
Core Method: thenReturn Chaining
Mockito's thenReturn method supports chaining to specify different return values for consecutive method calls. The basic syntax is:
when(mockObject.methodName()).thenReturn(value1, value2, value3);
Alternatively, using explicit chaining:
when(mockObject.methodName()).thenReturn(value1).thenReturn(value2).thenReturn(value3);
With this configuration, the first call returns value1, the second returns value2, and the third and subsequent calls return value3. This approach is straightforward and suitable for scenarios with known, fixed return value sequences.
Advanced Method: thenAnswer Custom Logic
For more complex requirements, the thenAnswer method provides complete control. By implementing the Answer interface, you can dynamically determine return values based on call count, parameter values, or other conditions. Example code:
when(someMock.someMethod()).thenAnswer(new Answer<Object>() {
private int count = 0;
public Object answer(InvocationOnMock invocation) {
if (count++ == 0)
return "First Call";
else if (count == 1)
return "Second Call";
else
return "Subsequent Calls";
}
});
Using Lambda expressions can further simplify the code:
when(someMock.someMethod()).thenAnswer(invocation -> {
// Custom logic to return different values based on context
return determineResponseBasedOnContext();
});
Practical Application: ExecutorCompletionService Testing Case
Consider the original problem's ExecutorCompletionService testing scenario. We need to mock the take().get() method to return different task results on multiple calls, verifying that business logic tolerates completion order variations. Implementation using thenAnswer:
ExecutorCompletionService<String> mockCompletionService = mock(ExecutorCompletionService.class);
Future<String> mockFuture = mock(Future.class);
when(mockCompletionService.take()).thenReturn(mockFuture);
when(mockFuture.get()).thenAnswer(new Answer<String>() {
private int callCount = 0;
public String answer(InvocationOnMock invocation) throws Throwable {
switch (callCount++) {
case 0: return "Result A";
case 1: return "Result B";
case 2: return "Result C";
default: return "Default Result";
}
}
});
This approach ensures that tests cover all possible execution paths regardless of actual task completion order.
Method Comparison and Selection Guide
thenReturn Chaining is suitable for:
- Fixed and known return value sequences
- Few and determined number of calls
- Scenarios prioritizing code simplicity
thenAnswer Custom Logic is suitable for:
- Return values determined by complex conditions
- Need to access invocation parameters or context
- Testing non-deterministic or random behaviors
Best Practices and Considerations
When using these techniques, consider the following:
- State Management: When using instance variables in
thenAnswerto track call state, ensure thread safety. - Exception Handling: Use
thenThrowto simulate method exceptions, enhancing test coverage. - Code Readability: Complex
thenAnswerlogic should be properly commented or extracted into separate methods. - Performance Considerations: For high-frequency calls,
thenReturnis generally more efficient thanthenAnswer.
Conclusion
Mockito provides powerful tools to simulate methods returning different results on multiple calls. thenReturn chaining suits simple sequences, while thenAnswer offers unlimited flexibility. Proper use of these techniques significantly improves unit test quality and coverage, especially when testing concurrent and asynchronous code. Developers should choose the most appropriate method based on specific testing needs, balancing code simplicity and functional power.