Mockito's when(mock.findById(1L)) receives the *result* of calling findById rather than a method reference. Explain how it can still register a stub for that call, and what practical consequences the mechanism has.
answer
- Argument evaluated first -> mock records the Invocation
- when() ignores its value, reads the last recording
- Matchers = a parallel stack popped at record time
- Spy: recording step runs real code
- Thread-local state; UnfinishedStubbing surfaces later
basics
~20 sJava evaluates the inner call first: the mock intercepts it, records the invocation (method plus arguments plus matchers) in Mockito's internal state, and returns a default. when() then ignores its argument and attaches the stub to that last recorded invocation. Hence real calls on spies, matcher rules, and unfinished-stubbing errors.
solid answer
~60 s`when(...)` never sees the call - it sees whatever the call returned. The trick is ordering: 1. Java evaluates `mock.findById(1L)` first. 2. The mock intercepts it, pushes an `Invocation` (method, arguments, any matchers) onto Mockito's thread-local state, and returns a default value. 3. `when(x)` discards `x` and asks Mockito for the last recorded invocation, converting it into an `OngoingStubbing`. 4. `.thenReturn(v)` attaches the behaviour to that invocation. Consequences worth naming: - On a **spy**, step 1 runs the real method - real side effects included. The `doReturn(...).when(spy).call()` form exists to avoid that. - Matchers are also pushed onto a stack during step 1, which is why mixing literals and matchers, or calling a matcher outside a stubbing, throws `InvalidUseOfMatchersException`. - Calling `when()` on something that is not a mock, or on a method Mockito cannot intercept, yields `MissingMethodInvocationException`. - Leaving a stubbing half-written surfaces later as `UnfinishedStubbingException`, often reported in an unrelated test. - The state is per thread, so stubbing concurrently while calls are in flight is unsupported.
code
java · 4 linesList<String> spy = spy(new ArrayList<String>());
// Throws IndexOutOfBoundsException: spy.get(0) is evaluated before when()
when(spy.get(0)).thenReturn("x");go deeper
State the ordering: the call runs first, the mock remembers it, and when() attaches behaviour to that remembered call.
Walk the four steps precisely and derive at least two consequences - the matcher all-or-nothing rule and the spy executing real code.
Use the mechanism as a diagnostic tool: map MissingMethodInvocation, UnfinishedStubbing and stray-matcher errors back to the recording step, including why they surface in the wrong test.
Comment on the design trade-off - a fluent, compile-checked API bought with hidden thread-local state - and what that implies for parallel test execution and for choosing frameworks.
## The puzzle `when(mock.findById(1L)).thenReturn(user)` looks impossible. In Java, arguments are evaluated before the method is entered, so by the time `when` runs, `mock.findById(1L)` has already executed and `when` receives only its return value - typically `null`. How can `when` know which method and arguments to stub? ## The answer: the mock records, when() reads the recording A Mockito mock is a generated subclass (or interface implementation) whose methods are intercepted. Every intercepted call, stubbing or not, is turned into an `Invocation` object - the mock, the `Method`, the arguments, a sequence number - and registered in Mockito's internal, **thread-local** progress state. The call then returns the default value for its return type. `when(T value)` ignores its parameter entirely. It asks Mockito for the invocation that was just recorded and wraps it in an `OngoingStubbing`. The subsequent `.thenReturn(...)` / `.thenThrow(...)` / `.thenAnswer(...)` attaches behaviour to that invocation and marks the stubbing complete. Mockito also removes that invocation from the recorded-interactions list, which is why a stubbing call does not later count as a real interaction for `verify()` or `verifyNoMoreInteractions()`. ## Consequence 1: spies really call the real method On `spy(new ArrayList<String>())`, step 1 is a real invocation on real state. `when(spy.get(0)).thenReturn("x")` on an empty list throws `IndexOutOfBoundsException` before `when` is ever reached. The same applies to any real method with side effects - a database write, an HTTP call, a counter bump. The `doReturn(...).when(spy).get(0)` family exists precisely because it never evaluates the call in a value position; that alternative form is its own topic, but the *reason* it exists is this recording mechanism. ## Consequence 2: argument matchers are a parallel stack `any()`, `eq(1L)` and friends do not return meaningful values. Each one pushes a matcher onto a stack and returns a dummy (`null` or `0`). When the invocation is recorded, Mockito pops the matchers and binds them positionally to the arguments. That design explains the classic rules and errors: - If you use a matcher for one argument you must use matchers for all - a raw `1L` leaves no matcher on the stack, so the counts disagree and you get `InvalidUseOfMatchersException`. - Calling a matcher outside `when`/`verify` (say, storing `any()` in a variable) leaves a stray matcher on the stack and corrupts the *next* stubbing, often producing a confusing error somewhere unrelated. - A matcher used as an argument to a real method - `when(mock.find(helper.wrap(any())))` - is evaluated in the wrong order and misbinds. ## Consequence 3: the failure modes have names - `MissingMethodInvocationException`: `when()` found no recorded invocation. Causes include passing a non-mock, stubbing a method that cannot be intercepted, or a mocking-framework misconfiguration. - `UnfinishedStubbingException`: an invocation was recorded for stubbing but never completed - for example calling a mock inside another stubbing's argument, or writing `when(mock.a())` with no `then...`. Because the state is validated lazily, this frequently blows up in the *next* test, which is why the message tells you to look for a stubbing you left open. - `WrongTypeOfReturnValue`: the stub's value does not fit the method's return type; often the real cause is that the recorded invocation was not the one you intended (a nested call, or a spy). ## Consequence 4: thread confinement Because the progress state is thread-local, stubbing must happen on the thread that will complete it, and stubbing a mock while other threads are calling it is unsupported - the recording of a concurrent call can be mistaken for the one being stubbed. Set up all stubs before starting concurrent work. ## Consequence 5: only interceptable methods can be stubbed The recording depends on interception. Anything the mock cannot intercept never reaches Mockito's state, so `when()` sees nothing recorded and fails. Historically that meant `final` methods, `static` methods and constructors were off-limits; modern Mockito has opt-in mechanisms for some of these, which live in their own topic. ## Why this is worth knowing The mechanism turns a family of otherwise arbitrary rules - matcher all-or-nothing, spy surprises, errors surfacing in the wrong test - into a single explanation. A candidate who can narrate the four steps can usually diagnose any of those failures without guessing.
- Why does a test sometimes fail with UnfinishedStubbingException in a test that contains no obvious stubbing mistake?Mockito's stubbing state is validated lazily, so an incomplete stubbing left behind by an earlier test - a when() with no then..., or a mock call nested inside another stubbing - is only detected when the next interaction with the framework occurs. The fix is in the earlier test, not the failing one; the exception message usually points at the location that opened the stubbing.
- Why must all arguments be matchers once you use one, and what breaks if you store a matcher in a variable for reuse?Matchers push onto a stack and return dummy values; at record time Mockito binds the stack to the arguments positionally, so the counts must line up - a raw literal contributes an argument but no matcher. Storing a matcher in a variable evaluates it outside a stubbing, leaving a stray entry on the stack that corrupts the next stubbing or verification, often with an error in an unrelated place.
The mock is a receptionist who writes down every visitor as they walk in; when() does not watch the door, it just reads the last line of the logbook.
saying these in an interview costs you the question
- Believing when() somehow captures the method reference or uses bytecode magic on the test
- Thinking a mock call written inside when() does not execute at all - it does, and on spies it runs real code
- Assuming argument matchers return usable values rather than dummies plus a stack push
- Reusing a matcher stored in a local variable across stubbings
- Stubbing a mock from another thread while the code under test is already calling it