when/thenReturn vs doReturn/when
Fast recall
Quick revision
Core answer
when(spy.read()).thenReturn(value) evaluates spy.read() while the stub is being configured, so a spy's real method runs first. doReturn(value).when(spy).read() installs the result without that call. Use normal when(...).thenReturn(...) for ordinary mocks; use doReturn when a real spy call during setup would be unsafe. Void methods use other do... forms such as doNothing or doThrow, not doReturn.Interview-ready guide
Interview answer
Both forms stub a method that returns a value, but they differ during setup. when(spy.load()).thenReturn(data) evaluates spy.load() first. A spy normally calls real methods, so that setup line can read a file, throw an exception, or use state that has not been prepared. doReturn(data).when(spy).load() installs the same result without running the real method at that moment. On a normal mock, I prefer when(...).thenReturn(...) because it is clearer and gives stronger type checking. I use doReturn for a spy only when the real setup call would be unsafe. For void methods, I use doNothing, doThrow, or doAnswer; doReturn is not the general void-method syntax.
when/thenReturn vs doReturn/when
Direct Answer
Both forms can stub a non-void method. when(spy.read()).thenReturn(value) evaluates spy.read() while the stub is being configured, so a spy's real method runs first. doReturn(value).when(spy).read() installs the result without that call. Use normal when(...).thenReturn(...) for ordinary mocks; use doReturn when a real spy call during setup would be unsafe. Void methods use other do... forms such as doNothing or doThrow, not doReturn.
Code Example
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
private List<String> orderList;
@Test
void whenThenCausesRealMethodCall() {
orderList = new ArrayList<>(List.of("order-1"));
var spy = spy(orderList);
// This CALLS list.get(0) during stubbing!
// If index is out of bounds, test setup fails
when(spy.get(0)).thenReturn("stubbed-order");
assertThat(spy.get(0)).isEqualTo("stubbed-order");
}
@Test
void doReturnAvoidsRealMethodCall() {
orderList = new ArrayList<>(); // Empty list
var spy = spy(orderList);
// This does NOT call list.get(0) during stubbing
// Safe even though index 0 doesn't exist
doReturn("stubbed-order").when(spy).get(0);
assertThat(spy.get(0)).isEqualTo("stubbed-order");
}
@Test
void demonstratingSideEffect() {
var service = spy(new OrderService());
// Real method increments counter during stubbing!
when(service.processOrder("ord-1")).thenReturn("OK");
// counter is now 1 from setup, not from actual test execution
doReturn("OK").when(service).processOrder("ord-2");
// counter remains 0, real method not called during stubbing
}
}
Common Mistakes & Pitfalls
- Using when().thenReturn() on spies without understanding side effects: Freshers create a spy, stub a method with when-then, and get confused when the real method executes during test setup, causing exceptions or unexpected state changes before the test even runs. They think Mockito is broken, not realizing the stubbing syntax itself triggered the real method.
- Mixing syntaxes inconsistently: Using do-return-when for some stubs and when-then for others on the same spy creates confusion about which methods were safely stubbed and which might have triggered real logic.
- Overusing doReturn everywhere: Some freshers learn about the spy issue and then use do-return-when for all stubs, even pure mocks, making tests unnecessarily verbose and harder to read when the simpler when-then syntax would be perfectly safe.
Check your understanding
Answer these without looking back at the article.
- Why does calling a real method on a spy during stubbing cause problems?
- Can you use doReturn on a full mock instead of a spy?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.