skip to content

Deep Stubs

RETURNS_DEEP_STUBS makes every intermediate call in a chain return an auto-created mock so you can stub a.b().c() in one line. Interviewers expect you to know both the mechanism and the caveat: needing it usually means a Law-of-Demeter violation.

on this pageshow

questions

4

What does Mockito's Answers.RETURNS_DEEP_STUBS do, how do you turn it on for a mock, and which failure does it exist to prevent?

level: middleimportance: must knowfreq 38%

answer

  1. default answer returns null → NPE on the second link
  2. auto-creates + caches a child mock per invocation
  3. @Mock(answer = Answers.RETURNS_DEEP_STUBS)
  4. same args → same child mock; different args → different
  5. mocks only, not spies; javadoc calls it a design smell

basics

~20 s

It makes a mock auto-create and cache a mock for each mockable return type, so chained calls like when(a.getB().getC()).thenReturn(x) work instead of throwing NullPointerException on the second link. Enable with @Mock(answer = Answers.RETURNS_DEEP_STUBS) or mock(A.class, RETURNS_DEEP_STUBS).

solid answer

~50 s

A default Mockito mock returns "empty" values — `null` for object return types. So stubbing a chain, `when(config.getServer().getPort()).thenReturn(8080)`, throws `NullPointerException` while *building the stub*, because `getServer()` returns `null` before `getPort()` is ever reached. `RETURNS_DEEP_STUBS` replaces the default answer: whenever a method returns a mockable type, the mock creates a child mock of that type and **caches it against that invocation**, so the same call with the same arguments always returns the same child. That makes the whole chain stubbable and verifiable. Enable it three ways: ```java @Mock(answer = Answers.RETURNS_DEEP_STUBS) Config config; Config c = mock(Config.class, RETURNS_DEEP_STUBS); Config c = mock(Config.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); ``` It applies to mocks, not spies. Different arguments produce different child mocks, so `repo.find("a").name()` and `repo.find("b").name()` can be stubbed independently. Mockito's own javadoc warns it usually signals a Law-of-Demeter problem, so treat it as a tool for third-party fluent APIs, not a default.

code

java · 11 lines
java
// without deep stubs: NPE while building the stub
Config config = mock(Config.class);
// when(config.getServer().getPort()).thenReturn(8080); // NPE
Server server = mock(Server.class);
when(config.getServer()).thenReturn(server);
when(server.getPort()).thenReturn(8080);

// with deep stubs
Config deep = mock(Config.class, Answers.RETURNS_DEEP_STUBS);
when(deep.getServer().getPort()).thenReturn(8080);
assertSame(deep.getServer(), deep.getServer()); // cached child

go deeper

for a junior

Know the NPE-on-a-chain symptom and the one-line fix via @Mock(answer = Answers.RETURNS_DEEP_STUBS).

for a middle

Explain that it is just a default answer that creates and caches child mocks per invocation, and that argument values key the cache.

for a senior

Add when it is legitimate — third-party fluent APIs, legacy characterization — and mention the chain-ends-at-a-non-mockable-type failure mode.

for a principal

Position it as a coupling decision: the test encodes the shape of an object graph, so accept it only at boundaries you do not own.

## The problem it solves Every Mockito mock has a *default answer* — the strategy used when an unstubbed method is called. The out-of-the-box default, `RETURNS_DEFAULTS`, returns a type-appropriate empty value: `0` for numeric primitives, `false` for boolean, an empty collection for collection types, and `null` for everything else. That breaks on chained calls. Given `when(config.getServer().getPort()).thenReturn(8080)`, Java evaluates the argument to `when(...)` first, so `config.getServer()` runs, returns `null`, and `.getPort()` throws `NullPointerException` — during test setup, before anything under test has executed. The traditional fix is three mocks and two stubs wired by hand: ```java Server server = mock(Server.class); when(config.getServer()).thenReturn(server); when(server.getPort()).thenReturn(8080); ``` Deep stubs automate exactly that. ## What deep stubs do With `RETURNS_DEEP_STUBS`, an unstubbed call whose return type is mockable does not return `null`; it creates a mock of that return type, itself a deep stub, and *records that as a stubbing on the parent*. The consequences follow from that: - **Chains are stubbable in one line.** `when(config.getServer().getPort()).thenReturn(8080)` just works. - **Child mocks are stable.** Because the child is stored as a stubbing keyed by the invocation, calling `config.getServer()` again returns the *same* mock — both in your test and in the code under test, which is what makes the technique usable at all. - **Argument-sensitive.** The key includes the argument matchers, so `repo.find("a")` and `repo.find("b")` yield different child mocks, and each chain can be stubbed independently. - **Deepness is unlimited.** Each child is itself a deep stub, so `a.b().c().d().e()` works to any depth. ## Turning it on Three equivalent forms: ```java @Mock(answer = Answers.RETURNS_DEEP_STUBS) HttpClientConfig config; HttpClientConfig config = mock(HttpClientConfig.class, RETURNS_DEEP_STUBS); HttpClientConfig config = mock(HttpClientConfig.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); ``` The `withSettings()` form is the one to use when you need to combine it with other settings such as a name or extra interfaces. `Answers.RETURNS_DEEP_STUBS` is an enum constant implementing `Answer`, so it is just the mock's default answer — nothing about it is special-cased in the API. It is a *mock* setting. Spies wrap a real instance and call real methods, so deep stubbing is not applicable there; if you find yourself wanting both, the design is telling you something. ## Scope of the effect The deep behaviour is inherited by the auto-created children, so the whole reachable graph becomes mocks. Any call you *do* stub explicitly overrides the auto-created child for that invocation. And a call returning a non-mockable type ends the chain by falling back to the ordinary empty value (`null` for `String`, `0` for `int`), which is the usual reason a "deep" chain still NPEs. ## When it is the right call Deep stubs earn their place with fluent third-party APIs you cannot change — SDK client builders, configuration trees, servlet-style request objects — and in characterization tests around legacy code. For your own code, the chain itself is the problem: pass the leaf collaborator in, or hide the traversal behind an adapter you own. Mockito's documentation says so explicitly, and an interviewer asking about deep stubs is very often asking whether you know that.

  • Why does when(config.getServer().getPort()).thenReturn(8080) throw a NullPointerException with an ordinary mock?
    Java evaluates the argument of `when(...)` before calling it, so `config.getServer()` executes first. On an ordinary mock that unstubbed call returns the default empty value, `null`, and dereferencing it with `.getPort()` throws immediately — during setup, not during the code under test. Deep stubs avoid it by returning a child mock instead of null.
  • If the code under test calls config.getServer() twice, does it get the same object both times?
    Yes. The auto-created child is recorded as a stubbing on the parent keyed by that invocation, so repeated calls with matching arguments return the identical mock. That stability is essential — otherwise stubbing a chain in the test would have no effect on the object the production code actually receives. Different arguments do produce different child mocks.
  • How is RETURNS_DEEP_STUBS different from RETURNS_SELF?
    `RETURNS_SELF` makes a method return the mock itself when the return type is compatible with the mocked type — built for builders whose methods return `this`. `RETURNS_DEEP_STUBS` returns a mock of whatever the declared return type is, so it handles chains that walk through different types. Use RETURNS_SELF for fluent builders, deep stubs for graph traversal.

Ordinary mocks are a phone directory that answers "no such number" for anything you did not write down. A deep stub invents a plausible extension on demand — and remembers it, so calling again reaches the same imaginary person.

saying these in an interview costs you the question

  • Saying deep stubs make the mock return real objects rather than more mocks
  • Thinking each call in the chain returns a fresh, different mock
  • Believing you can apply RETURNS_DEEP_STUBS to a spy
  • Assuming deep stubs are the recommended default for all mocks
  • Confusing it with RETURNS_SMART_NULLS, which only improves the error message rather than enabling chains

context

open as a page

A Mockito deep-stub chain still fails: one call in the middle returns null, and another throws ClassCastException on a method declared to return a generic type variable T. What are the limits of deep stubbing that explain both?

level: middleimportance: should knowfreq 22%

basics

~20 s

A deep stub only creates a child mock when the return type is mockable; otherwise it falls back to the plain default value, so String returns null and int returns 0, ending the chain. For a method returning a type variable, erasure leaves only the bound (often Object), so the child mock is of the wrong type and the cast fails.

open as a page

With a Mockito mock created using RETURNS_DEEP_STUBS, how do you verify an interaction on the last object of a call chain, and why are invocation counts on the intermediate objects untrustworthy?

level: seniorimportance: should knowfreq 20%

basics

~20 s

Verify on the leaf: verify(config.getServer()).restart() works because getServer() returns the cached child mock. But every call to getServer() — from production code, from your stubbing, and from inside the verify argument — is recorded on the parent, so times(n) on chain links and verifyNoMoreInteractions are misleading.

open as a page

Mockito's own documentation warns that RETURNS_DEEP_STUBS usually means something is wrong with the design. What is the underlying argument, when would you accept deep stubs anyway, and what would you reach for first instead?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

A deep stub encodes a call chain, so the test depends on the shape of an object graph rather than on behaviour — refactoring the intermediate types breaks tests that never changed meaning. Accept it for third-party fluent APIs and legacy characterization; otherwise inject the leaf collaborator, use real value objects, or hide the chain behind an adapter you own.

open as a page