skip to content

What does creating a Mockito mock with Answers.RETURNS_DEEP_STUBS do, and when is choosing it a warning sign rather than a solution?

level: seniorimportance: nice to knowfreq 32%

answer

  1. every hop returns another deep-stub mock, cached per call
  2. one-line when(a.getB().getC())
  3. Law of Demeter smell over your own types
  4. silent mocks instead of an early NPE
  5. RETURNS_SELF for builders; real objects for data

basics

~20 s

Deep stubs make every object-returning method return another deep-stub mock, remembered per call, so when(a.getB().getC()).thenReturn(x) works without stubbing each level. It is convenient for hostile third-party APIs, but a chain you must deep-stub usually signals a Law-of-Demeter violation in your own code.

solid answer

~50 s

`mock(Foo.class, Answers.RETURNS_DEEP_STUBS)` (or `@Mock(answer = RETURNS_DEEP_STUBS)`) changes the default answer so that any method returning a mockable type returns a child mock, itself a deep stub, and the same child is returned for the same call. That makes chained stubbing and chained calls work in one line instead of building a pyramid of intermediate mocks. Limits: it cannot help where the chain returns a final class the mock maker cannot mock, primitives, or generic types erased past recognition; verification on a deep chain is awkward because you must reach the intermediate mock; and the failure modes are opaque — a typo mid-chain silently produces yet another mock. The design point: if the chain is over *your* code, it is telling you the unit reaches through its collaborator, and the fix is to pass what it needs directly or add a method on the collaborator. Deep stubs are legitimate mostly for third-party fluent/config APIs you cannot restructure.

code

java · 11 lines
java
// without deep stubs: one mock per level
Order order = mock(Order.class);
Customer customer = mock(Customer.class);
Address address = mock(Address.class);
when(order.getCustomer()).thenReturn(customer);
when(customer.getAddress()).thenReturn(address);
when(address.getCountryCode()).thenReturn("DE");

// with deep stubs: one line
Order deep = mock(Order.class, Answers.RETURNS_DEEP_STUBS);
when(deep.getCustomer().getAddress().getCountryCode()).thenReturn("DE");

go deeper

for a junior

Know it exists and that it lets you stub a chained call in one line; you are not expected to use it.

for a middle

Explain the child-mock mechanism and the creation-time setting, plus the fact that it hides typos in the chain.

for a senior

Lead with the design judgement — acceptable against third-party APIs, a Law-of-Demeter smell over your own model — and name RETURNS_SELF and real object graphs as better tools.

for a principal

Treat frequent deep stubbing as an architectural metric: it maps exactly onto units that reach through their collaborators, and the remedy is interface design, not test configuration.

## What the answer does A mock's default answer decides what an unstubbed method returns. `RETURNS_DEEP_STUBS` does two things: 1. When an unstubbed method returns a type Mockito can mock, it returns a **new mock of that type, also configured with deep stubs** — rather than `null`. 2. It **remembers** the child per invocation, so calling the same method with the same arguments twice returns the *same* child mock. That memory is what makes `when(a.getB().getC()).thenReturn(x)` work: the stubbing recorded on the child survives, and the production call walking the same path lands on the same object. Without deep stubs, the same test needs a mock per level: ```java A a = mock(A.class); B b = mock(B.class); C c = mock(C.class); when(a.getB()).thenReturn(b); when(b.getC()).thenReturn(c); when(c.value()).thenReturn("x"); ``` With deep stubs it collapses to one line. ## Where it genuinely helps - **Third-party fluent or hierarchical APIs** you cannot change: a servlet/HTTP client request object, a cloud SDK builder, a configuration tree, a JPA `CriteriaBuilder`. You need one leaf value and the API forces you through four hops. - **Fluent builders** where each call returns the builder — though `Answers.RETURNS_SELF` is the more precise tool there, since it returns the mock itself for methods returning the mock's own type. ## Where it hurts 1. **It hides a design smell in your own code.** `order.getCustomer().getAddress().getCountry().getCode()` in production means the unit knows the shape of four types it does not own. Passing the country code (or asking `order.customerCountryCode()`) removes both the chain and the deep stub. Mockito's own javadoc says deep stubs are a hint that you may be violating the Law of Demeter, and that they should be used "occasionally", not habitually. 2. **Opaque failures.** Because every unstubbed hop returns another mock instead of null, a wrong path does not fail — it silently produces a mock, and the assertion at the end fails for a reason that looks unrelated. With ordinary defaults you would have got an immediate NPE pointing at the exact hop. 3. **Verification gets awkward.** To verify a call on the third level you must re-navigate the chain (`verify(a.getB().getC()).value()`), which reads badly and, because navigating also *records* invocations, interacts confusingly with `verifyNoMoreInteractions`. 4. **Type limits.** The chain stops working where a hop returns a `final` class the current mock maker cannot mock, a primitive, a `String`, or a type erased to something unmockable; you then get the plain default and a puzzling NPE. Generic return types can also resolve to the erasure rather than the type you expected. 5. **Cost.** Every hop mints and caches a mock, so a wide deep-stub tree in a large suite is measurable memory. ## Alternatives, roughly in order of preference - **Change the production code** so the unit takes what it needs (a value or a narrow interface) instead of walking a graph. - **Use a real object graph** — for value/data types, building a real `Order` with a real `Customer` is usually shorter and far more readable than mocking three levels. Test data builders shine here. - **`RETURNS_SELF`** for fluent builders specifically. - **A hand-rolled fake** for a complex third-party API used across many tests; a single fake beats deep stubs replicated in twenty test methods. - **Deep stubs**, when the chain belongs to code you cannot restructure and the alternative is a pile of intermediate mocks that adds no clarity. ## Mechanics worth remembering - It is a **creation-time** setting: `mock(Type.class, RETURNS_DEEP_STUBS)`, `withSettings().defaultAnswer(RETURNS_DEEP_STUBS)`, or `@Mock(answer = Answers.RETURNS_DEEP_STUBS)`. You cannot turn a normal mock into a deep-stub mock afterwards. - Deepness is inherited: children are deep stubs too, all the way down. - Stubbing a hop explicitly overrides the generated child from that point on. - Deep stubs and spies do not combine well; a spy already runs real code, and layering deep stubs over partial reality produces behaviour nobody can predict from reading the test. ## Interview framing Explain the mechanism (child mocks, remembered per call, enabling chained stubbing), then immediately take the design position: acceptable against third-party APIs, a smell over your own model, and it trades an early precise NPE for a late confusing assertion failure. Naming `RETURNS_SELF` for builders and "just build a real object" as the usual better answer is what separates a senior response from a feature recital.

  • How do you verify a call on the third level of a deep-stub chain, and why is that unpleasant?
    You have to navigate to the intermediate mock again, e.g. `verify(order.getCustomer().getAddress()).getCountryCode()`. It reads badly, and the navigation itself records invocations on the parent mocks, which then confuses `verifyNoMoreInteractions`. If a test needs verification deep in a chain, that is strong evidence the collaboration should be flattened rather than deep-stubbed.
  • Your team's tests mock a domain aggregate with deep stubs three levels down. What would you propose instead?
    Build the real aggregate with a test data builder — domain objects are usually cheap to construct and carry their own invariants, so a real graph is both shorter and more truthful than three levels of mocks. If construction is genuinely painful, that is a signal to fix the constructors or introduce a builder. Reserve deep stubs for third-party types you cannot restructure.

Deep stubs are like an automatic receptionist who invents a plausible department for every transfer you ask for: you never hear 'no such extension', so a wrong number only shows up as a strange conversation at the end.

saying these in an interview costs you the question

  • Presenting deep stubs as the normal way to mock collaborators
  • Believing deep stubs work for chains that return final classes, String or primitives
  • Not realizing an unstubbed hop silently yields another mock instead of failing
  • Trying to enable deep stubs after the mock has been created
  • Combining deep stubs with a spy and expecting predictable behaviour

context