Appearance
Stubbing: Matchers, Static and Dynamic Answers
The architecture chapter showed how stubbing works inside Mockito. This chapter covers every way to write a stub.
Without a stub, a mock returns a default: null for objects, 0 for numbers, false for boolean, an empty collection, Optional or stream. Stubbing overrides the default.
java
@Test
void testWithoutStub() {
Calculator calculator = Mockito.mock(Calculator.class);
int sum = calculator.add(4, 2);
assertEquals(6, sum); // FAILS: a mock returns 0, it never runs the real add
}1. when(...).thenReturn(...)
Return a value when the method is called with these exact arguments:
java
Calculator calculator = Mockito.mock(Calculator.class);
when(calculator.add(4, 2)).thenReturn(10);
assertEquals(10, calculator.add(4, 2));
assertEquals(0, calculator.add(5, 4)); // arguments differ: the stub does not apply, default 0If several stubs match a call, the most recently defined one wins.
2. when(...).thenThrow(...)
Make the call throw, to test how the code under test handles failures.
Unchecked exceptions (subclasses of RuntimeException; the compiler does not force you to handle them) can be thrown from any method:
java
when(calculator.add(4, 2)).thenThrow(new NullPointerException());
assertThrows(NullPointerException.class, () -> calculator.add(4, 2));Checked exceptions (the compiler forces a try/catch or a throws) can be thrown only if the stubbed method declares them:
java
public int add(int a, int b) throws IOException { ... }java
@Test
void testCheckedException() throws IOException { // calling add() requires handling IOException
Calculator calculator = Mockito.mock(Calculator.class);
when(calculator.add(4, 2)).thenThrow(new IOException());
assertThrows(IOException.class, () -> calculator.add(4, 2));
}Stubbing a checked exception that the method does not declare fails at once: "Checked exception is invalid for this method! Invalid: java.io.IOException". A real add could never throw it, so the test would be testing an impossible situation.
3. Consecutive stubbing
Chain answers to return different results on successive calls:
java
when(service.get("u123"))
.thenReturn(null) // 1st call
.thenReturn("Alice"); // 2nd call, and every call after itThe last answer repeats for all further calls. Answers can mix values and exceptions:
java
when(calculator.add(4, 2))
.thenReturn(5)
.thenReturn(6)
.thenThrow(new NullPointerException())
.thenReturn(7);
assertEquals(5, calculator.add(4, 2));
assertEquals(6, calculator.add(4, 2));
assertThrows(NullPointerException.class, () -> calculator.add(4, 2));
assertEquals(7, calculator.add(4, 2));
assertEquals(7, calculator.add(4, 2)); // last value repeatsShorthand: thenReturn(5, 6, 7).
4. doReturn(...).when(...)
Stubs without calling the real method while stubbing, so it is the safe choice for spies:
java
Calculator calculator = spy(new Calculator());
when(calculator.add(4, 2)).thenReturn(100); // returns 100 later, but runs the real add() now
doReturn(100).when(calculator).add(4, 2); // runs nothing now
int sum = calculator.add(4, 2); // 100Why, see the architecture chapter. One trade-off: doReturn takes an Object, so the compiler does not check that the value fits the method's return type; a mismatch only fails when the test runs.
5. doNothing() for void methods
java
class NotificationService {
void sendEmail(String userId) { ... } // sends a real email
}On a mock, a void method already does nothing; no stub is needed:
java
NotificationService mockNotify = Mockito.mock(NotificationService.class);
mockNotify.sendEmail("u1"); // does nothingOn a spy, an unstubbed call runs the real method and would send the email. doNothing() stops that:
java
NotificationService spyNotify = Mockito.spy(new NotificationService());
doNothing().when(spyNotify).sendEmail(anyString());
spyNotify.sendEmail("u1"); // does nothing6. doThrow() for void methods
when(...) needs a method call that returns a value, because the call is its argument. A void call cannot be an argument:
java
when(mockNotify.sendEmail("u1")).thenThrow(new RuntimeException()); // does not compileUse doThrow instead:
java
@Test
void testDoThrow() {
NotificationService mockNotify = Mockito.mock(NotificationService.class);
doThrow(new RuntimeException()).when(mockNotify).sendEmail("u1");
assertThrows(RuntimeException.class, () -> mockNotify.sendEmail("u1"));
}The do... family (doReturn, doThrow, doNothing, doAnswer) works for every method, void or not, mock or spy. when(...) is more readable and type-checked, so it is the usual choice for non-void methods on mocks.
7. Argument matchers
Exact arguments are often too strict: when(calculator.add(4, 2)) does not apply to add(5, 4). Matchers match arguments flexibly:
java
when(calculator.add(anyInt(), anyInt())).thenReturn(10);
assertEquals(10, calculator.add(4, 2));
assertEquals(10, calculator.add(5, 4));Most matchers are static methods of ArgumentMatchers (also reachable through Mockito); the comparison ones are in AdditionalMatchers.
| Matcher | Matches | Example |
|---|---|---|
any() | anything, including null | when(service.process(any())).thenReturn("ok") |
any(User.class) | any User, not null | when(repo.save(any(User.class))).thenReturn(user) |
anyString() | any String, not null | when(service.getUser(anyString())).thenReturn(user) |
anyInt(), anyLong(), anyDouble(), anyBoolean() | any value of that type | when(repo.findById(anyLong())).thenReturn(entity) |
anyList(), anyMap(), anySet() | any non-null collection of that type | when(service.process(anyList())).thenReturn(true) |
eq(value) | exactly this value | when(calculator.add(anyInt(), eq(5))).thenReturn(15) |
isNull(), notNull() | null / anything but null | when(service.process(isNull())).thenReturn("empty") |
gt(n), lt(n), geq(n), leq(n) | greater / less than (or equal to) n | when(service.calculate(gt(100))).thenReturn("high") |
contains(s), startsWith(s), endsWith(s) | a string containing / starting / ending with s | when(service.getUser(startsWith("user_"))).thenReturn(user) |
matches(regex) | a string matching the regex | when(service.getUser(matches("user\\d+"))).thenReturn(user) |
Since Mockito 2, the typed any... matchers do not match null: anyString() and anyList() reject null; only any() accepts it.
The matcher rule: if one argument uses a matcher, all must. Mixing a matcher with a raw value fails:
java
when(calculator.add(anyInt(), 5)).thenReturn(15); // WRONGtext
Invalid use of argument matchers!
2 matchers expected, 1 recordedWrap the fixed value in eq():
java
when(calculator.add(anyInt(), eq(5))).thenReturn(15); // correct8. Dynamic stubbing: thenAnswer() and doAnswer()
All stubs so far are static: a fixed value per call. An answer is a lambda that runs on each call, with access to the call's arguments, and computes the result.
The class under test:
java
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public void register(User user) {
repository.save(user);
}
public User getUser(Long id) {
return repository.findById(id);
}
}With static stubbing:
java
@Test
void testWithoutDynamicStubbing() {
UserRepository repository = mock(UserRepository.class);
UserService userService = new UserService(repository);
User user1 = new User(1L, "A");
User user2 = new User(2L, "B");
userService.register(user1); // save() on a mock does nothing anyway
userService.register(user2);
when(repository.findById(1L)).thenReturn(user1);
when(repository.findById(2L)).thenReturn(user2);
assertEquals("A", userService.getUser(1L).getName());
assertEquals("B", userService.getUser(2L).getName());
}It passes, but it is weak:
- Every new user needs its own stub: 10 users, 10 more stubs.
- It does not test the real behavior: what is saved can be found again.
savestores nothing, andfindByIdreturns whatever we wired up by hand; the test would pass even ifregisternever calledsave.
With dynamic stubbing, the mock behaves like a tiny in-memory repository:
java
@Test
void testWithDynamicStubbing() {
UserRepository repository = mock(UserRepository.class);
UserService userService = new UserService(repository);
Map<Long, User> store = new HashMap<>();
// save: put the argument in the map (save is void, so doAnswer)
doAnswer(invocation -> {
User user = invocation.getArgument(0);
store.put(user.getId(), user);
return null; // a void method's answer returns null
}).when(repository).save(any());
// findById: read from the map
when(repository.findById(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
return store.get(id);
});
userService.register(new User(1L, "A"));
userService.register(new User(2L, "B"));
assertEquals("A", userService.getUser(1L).getName());
assertEquals("B", userService.getUser(2L).getName());
}invocation.getArgument(i) returns the call's i-th argument (0-based). Now, if register stopped calling save, getUser would find nothing and the test would fail.
When to use which:
- Stateless dependency (the output depends only on the input): static stubbing is enough.
- Stateful dependency (the output depends on earlier calls): dynamic stubbing. Examples: a cache (
getdepends onput), a repository (finddepends onsave), retry logic (fail on the first calls, succeed later; consecutive stubbing also covers simple cases).
Strict stubs: unused stubs fail the test
With MockitoExtension, Mockito runs in strict stubs mode. A stub that the test never uses fails the test after it runs:
java
@ExtendWith(MockitoExtension.class)
class CalculatorTest {
@Mock
Calculator calculator;
@Test
void test() {
when(calculator.add(4, 2)).thenReturn(10);
calculator.add(5, 4); // different arguments: the stub is never used
}
}text
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at CalculatorTest.test(CalculatorTest.java:10)This keeps tests clean, and often catches a real bug: the code called the dependency with different arguments than the test expected. If a stub is deliberately not used in every test (for example, one shared by several tests), mark it lenient:
java
lenient().when(calculator.add(4, 2)).thenReturn(10);Mocks created with Mockito.mock() outside the extension are not checked.
Next: Stubbing controls what a dependency returns. Chapter 19, the last, checks how the code under test used it: verification.
Check yourself
- What does an unstubbed mock method return for
int,boolean,StringandList? - After
when(calculator.add(4, 2)).thenReturn(10), what doesadd(5, 4)return? - When can a stub throw a checked exception, and what happens otherwise?
- With
thenReturn(1).thenReturn(2), what does the third call return? - Why can
when(...)not stub a void method, and what do you use instead? - Is
doNothing()needed for a void method on a mock? On a spy? - What is wrong with
when(calculator.add(anyInt(), 5)), and how do you fix it? - Does
anyString()matchnull? Doesany()? - When is dynamic stubbing better than static stubbing? Give an example.
- Under
MockitoExtension, what happens to a stub the test never uses, and how can you allow it?
Answers
0,false,null, an empty list.0: the arguments do not match, so the default applies.- Only if the stubbed method declares it; otherwise Mockito fails with "Checked exception is invalid for this method!".
2: the last answer repeats.whentakes the call's return value as its argument, and a void call has none (it does not compile). UsedoThrow,doNothingordoAnswerwith.when(mock).method(...).- On a mock, no: void methods already do nothing. On a spy, yes, otherwise the real method runs.
- It mixes a matcher with a raw value ("Invalid use of argument matchers"). Use
eq(5). anyString(): no (since Mockito 2).any(): yes.- When the dependency is stateful and later calls depend on earlier ones, e.g. a repository where
findByIdmust return whatsavestored, or a cache. - The test fails with
UnnecessaryStubbingException("Unnecessary stubbings detected"). Uselenient().when(...).