Why should you use doReturn(...).when(spy) instead of when(spy...).thenReturn(...) with @MockitoSpyBean?
answer
- when(spy.m()) calls real m() first
- doReturn().when(spy).m() = no real call
- Spies: prefer do* family
- doReturn not compile-time type-checked
- void spy stub → doNothing/doThrow/doAnswer
basics
~10 sWith a spy, when(spy.method()).thenReturn(x) actually calls the real method while setting up the stub, which can cause side effects or exceptions. doReturn(x).when(spy).method() stubs without invoking the real method.
solid answer
~40 sBecause a spy delegates to the real object, the classic when(spy.doThing()).thenReturn(v) form evaluates spy.doThing() *first* to pass its result into when() — that runs the real implementation before the stub is installed, which can throw, mutate state, or hit a database. The safe idiom is doReturn(v).when(spy).doThing(): here Mockito intercepts doThing() as a stub target without executing it. This matters far more for spies than for full mocks (whose real methods do nothing anyway). Use doReturn/doThrow/doAnswer with spies as a rule of thumb. The trade-off is that doReturn is not type-checked at compile time, so a wrong return type only fails at runtime.
code
java · 14 lines@MockitoSpyBean
PricingService pricing;
@Test
void stubbingWithoutSideEffects() {
// BAD: real getRate() runs here (may hit network / throw)
// when(pricing.getRate("EUR")).thenReturn(1.1);
// GOOD: real getRate() is never invoked
doReturn(1.1).when(pricing).getRate("EUR");
assertThat(pricing.getRate("EUR")).isEqualTo(1.1);
verify(pricing).getRate("EUR");
}go deeper
Aware that spies have a stubbing gotcha even if not the exact reason.
Must explain that when(spy.m()) runs the real method and doReturn avoids it.
Adds the type-safety trade-off and void-method handling.
Notes shared-bean state pollution risk when a spy's real method fires during setup in a cached context.
This is one of the most important practical gotchas with **@MockitoSpyBean** (and any Mockito spy). **The problem.** Mockito has two stubbing styles: 1. `when(spy.method()).thenReturn(value)` — the *when/thenReturn* style. 2. `doReturn(value).when(spy).method()` — the *doReturn/when* style. Java evaluates arguments before calling a method. In style 1, `spy.method()` inside `when(...)` is **actually invoked** so its return value can be handed to `when()`. On a full mock this is harmless — the method is empty and returns a default. But on a **spy**, `spy.method()` runs the **real implementation**. If that real method: - throws an exception (e.g. the very condition you're trying to stub away), - performs a side effect (writes to a DB, sends an event, mutates state), - or is slow, then the stub setup itself triggers that behavior — often failing the test before your assertions even run. **The fix.** Style 2, `doReturn(value).when(spy).method()`, never calls the real `method()`. Mockito's `when(spy)` returns a proxy that records the *next* call as the method to stub, so `method()` is intercepted, not executed. The same applies to `doThrow(...)`, `doAnswer(...)`, `doNothing()`, and `doCallRealMethod()`. **Rule of thumb:** with spies, prefer the `do*` family. With plain mocks, either style works and `when/thenReturn` reads more fluently. **Trade-off / edge case.** `doReturn(...)` takes an `Object`, so the compiler cannot check that the value matches the method's declared return type — a mismatch surfaces only at runtime as a `WrongTypeOfReturnValue`/ClassCastException-style failure. The `when/thenReturn` form *is* compile-time type-checked. So the guidance is: use `when/thenReturn` for mocks and where the real method is safe to call; use `doReturn` whenever calling the real method would be harmful — i.e. almost always for spies. **void methods:** For a `void` method on a spy you *must* use `doNothing()`, `doThrow()`, or `doAnswer()` — `when(spy.voidMethod())` doesn't compile because `void` can't be an argument. **Interaction with @MockitoSpyBean specifically:** because the spy is a live Spring bean shared with the rest of the context, an accidental real invocation during stub setup can pollute shared state (caches, in-memory repositories) for the whole test, making this idiom especially important.
- What's the downside of always using doReturn over when/thenReturn?doReturn(...) accepts an Object, so it isn't compile-time type-checked — passing a value of the wrong type compiles fine and only fails at runtime. when/thenReturn catches such mismatches at compile time.
- How do you stub a void method on a spy?Use the do* family: doNothing().when(spy).method(), or doThrow(ex).when(spy).method(), or doAnswer(...). You can't wrap a void call inside when(...) because void isn't a valid argument.
saying these in an interview costs you the question
- Claiming both styles behave identically on a spy
- Wrapping a void method call inside when(...)
- Not realizing when(spy.m()) executes the real method during setup