A teammate tries to stub a method whose return type is void by writing when(service.delete(id)).thenThrow(new IllegalStateException()) in a Mockito test, and it will not compile. Explain why, and show the form that does work.
answer
- void is not an expression → cannot be an argument
- doThrow(...).when(mock).call()
- when(mock) puts mock in stubbing mode; next call is recorded
- checked exception must be declared
- doAnswer for void must return null
basics
~10 swhen(...) takes a value as its argument, and a void call produces no value, so the expression is not legal Java. Use the do-family instead: doThrow(new IllegalStateException()).when(service).delete(id) — behaviour first, stubbed call last.
solid answer
~50 s`when(T value)` is an ordinary method that takes the *result* of the mocked call. A `void` method has no result, so `when(service.delete(id))` cannot even be written — the compiler rejects passing a void expression as an argument. This is a Java language limitation, not a Mockito API gap. Mockito's answer is the mirrored do-family syntax: ```java doThrow(new IllegalStateException()).when(service).delete(id); doNothing().when(service).delete(id); doAnswer(inv -> { log.add(inv.getArgument(0)); return null; }) .when(service).delete(id); ``` Read it as "do this ... when this call happens". Mechanically, `when(service)` returns the mock in stubbing mode, so the *next* invocation on it (`delete(id)`) is recorded as the call being stubbed rather than executed. Two details: a checked exception must be declared by the method or Mockito rejects the stubbing immediately, and a `doAnswer` lambda for a void method must still `return null` because `Answer` is generic.
code
java · 8 linesdoNothing().when(service).delete(1L);
doThrow(new IllegalStateException("locked")).when(service).delete(1L);
doAnswer(inv -> {
deletedIds.add(inv.getArgument(0, Long.class));
return null;
}).when(service).delete(anyLong());go deeper
Say plainly that a void call has no value so it cannot be an argument to when(...), and show doThrow(...).when(mock).call().
Explain the mechanism — when(mock) returns the mock in stubbing mode and the next invocation is recorded — and cover doNothing/doThrow/doAnswer semantics including the checked-exception validation.
Add matcher rules on the recorded call, the doAnswer-returns-null detail, and when the do-family is required beyond void (spies, generics).
Note that heavy reliance on void stubbing with doAnswer to drive callbacks often indicates a collaborator protocol that would be clearer as a returned value or a small interface, and set team conventions accordingly.
## The root cause is Java, not Mockito Mockito's classic stubbing syntax reads like a DSL but is plain Java: ```java when(repo.findById(1L)).thenReturn(entity); ``` The expression `repo.findById(1L)` is evaluated *first*; its value is then passed to the static method `when(T value)`. On a mock, that invocation is intercepted and recorded, and `when(...)` picks up the recorded invocation from Mockito's internal state — the value handed in is only there to make the compiler and the generics work. That design collapses for `void`. In Java, a call to a `void` method is a statement, not an expression with a value; you cannot pass it as an argument to anything. `when(service.delete(id))` therefore fails at compile time with something like "'void' type not allowed here". No overload of `when` can fix this, because the language will not let a void expression appear in argument position at all. ## The mirrored syntax Mockito solves it by inverting the order so the void call is a statement in its own right: ```java doThrow(new IllegalStateException()).when(service).delete(id); ``` Step by step: 1. `doThrow(...)` returns a `Stubber` holding the behaviour you want. 2. `stubber.when(service)` switches that mock into stubbing mode and returns the mock itself. 3. `delete(id)` is invoked on the returned mock. Because the mock is in stubbing mode, the invocation is **recorded and not executed**; Mockito binds the behaviour from step 1 to that invocation and its argument matchers. The whole family follows this shape: `doNothing()`, `doThrow(...)`, `doAnswer(...)`, `doReturn(...)` and `doCallRealMethod()`. For a void method the meaningful ones are `doNothing`, `doThrow` and `doAnswer`. ## The individual members, for void methods **`doNothing()`** — the explicit no-op. On a plain mock it is redundant, because Mockito's default behaviour for an unstubbed void method is already to do nothing; it becomes load-bearing on a spy (where the real body would otherwise run) and as a link in a chain of consecutive behaviours. Applying it to a value-returning method raises `MockitoException: Only void methods can doNothing()`. **`doThrow(...)`** — makes the call fail. Accepts either an instance (`new IOException("boom")`) or a class (`IOException.class`, which Mockito instantiates). If the exception is checked and the method does not declare it, Mockito refuses the stubbing right there with "Checked exception is invalid for this method" — a stub-time failure that points at the stubbing line rather than a confusing failure later. **`doAnswer(...)`** — runs custom logic. The lambda receives an `InvocationOnMock` from which you can read arguments (`inv.getArgument(0)`), which is how you simulate a collaborator that mutates its argument or invokes a callback: ```java doAnswer(inv -> { Callback cb = inv.getArgument(1); cb.onSuccess("ok"); return null; // required: Answer<T> must return something }).when(service).submit(job, callback); ``` The `return null` surprises people. `Answer<T>` is generic over the return type, and for `void` Mockito expects `null`; a lambda body with no return will not compile against the functional interface unless you use `doAnswer` with an explicit block returning null (or `Mockito.doAnswer` with `AdditionalAnswers` helpers). ## Argument matchers work the same way The recorded invocation carries its argument matchers, so all the normal rules apply: ```java doThrow(new IllegalStateException()).when(service).delete(anyLong()); ``` As with any Mockito stubbing, matchers are all-or-nothing per call: if one argument uses a matcher, every argument must (`eq(1L)` for the literal ones), otherwise you get `InvalidUseOfMatchersException`. ## Verification looks similar but is not the same `verify(service).delete(id)` uses a different entry point (`Mockito.verify`) that also returns the mock in a special mode. Beginners sometimes blur the two and write `doThrow(...).verify(...)`; keep them separate — `when` on a `Stubber` arranges, `verify` asserts. ## Practical guidance Use the value-returning `when(...).thenReturn(...)` form wherever it compiles, since it is type-checked. Reach for the do-family when the method returns `void`, when you are stubbing a spy, or when generics defeat `thenReturn`. And remember the shorthand for reading the syntax aloud: behaviour first, target call last.
- Why does a doAnswer lambda for a void method still have to return null?Because the functional interface is Answer<T>, whose answer(InvocationOnMock) method returns T. Mockito has no void-specific variant for doAnswer, so the lambda must produce a value, and for a void stubbing that value is ignored — null is the conventional choice. If you forget it, the lambda does not compile against the interface.
- What does Mockito do if you stub a checked exception the method does not declare?It fails immediately when the stubbing is recorded, throwing a MockitoException whose message says the checked exception is invalid for this method. That is deliberate: the mock must not be able to do something the real type could never do. Unchecked exceptions and Errors are always permitted, so the constraint only bites for checked types.
saying these in an interview costs you the question
- Claiming Mockito 'just doesn't support' stubbing void methods
- Trying to make when(...) work by casting the void call or wrapping it in a lambda
- Writing a doAnswer lambda for a void method without returning null and expecting it to compile
- Thinking doNothing() is mandatory on a plain mock's void methods
- Mixing raw values and matchers in the recorded call and then blaming the do-family syntax