In Mockito, what does Mockito.spy(new ArrayList<String>()) give you that Mockito.mock(ArrayList.class) does not — specifically, what happens when you call a method you never stubbed?
answer
- Mock: unstubbed → default value (null/0/false)
- Spy: unstubbed → real method runs
- Both record calls; both verifiable
- mock(ArrayList).size() == 0 after add
- Stub spies with doReturn().when(spy)
basics
~20 sOn a mock, an unstubbed method does nothing and returns a default — null, 0, false, or an empty collection. On a spy, an unstubbed method runs the real implementation. Both record calls, so both can be verified.
solid answer
~50 sThe difference is the **default answer for unstubbed calls**. `Mockito.mock(ArrayList.class)` returns an object with no real behaviour: `add("a")` does nothing, `size()` returns `0`, `get(0)` returns `null`. Every method is replaced by a stub returning the type's default value until you say otherwise. `Mockito.spy(new ArrayList<>())` starts from a real object. An unstubbed `add("a")` really adds, and `size()` really returns `1`. Only the methods you explicitly stub deviate. Both are Mockito-instrumented, so both record invocations and both work with `verify(...)`. Practically: use a mock when you do not care about the collaborator's behaviour and only want to control inputs and assert interactions — which is nearly always. Use a spy when you need the object's real behaviour but want to override one method or assert that a particular call happened. Reaching for a spy by default usually means you are testing a class you should have injected as a real object instead.
code
java · 15 lines@Test
void mockVsSpy() {
List<String> mocked = mock(ArrayList.class);
mocked.add("a");
assertEquals(0, mocked.size()); // real add() never ran
List<String> spied = spy(new ArrayList<String>());
spied.add("a");
assertEquals(1, spied.size()); // real add() ran
verify(spied).add("a"); // spies are verifiable too
doReturn("z").when(spied).get(0); // stub one method, keep the rest real
assertEquals("z", spied.get(0));
}go deeper
State the core difference crisply — unstubbed calls return defaults on a mock and run the real code on a spy — and that both can be verified.
Add the stubbing pitfall (doReturn over when) and articulate when a spy is justified versus just using the real object.
Position spies as a targeted tool for legacy or partial-behaviour cases, and explain why widespread spying weakens isolation.
Frame the choice as an isolation policy: what the suite treats as the unit boundary, and why defaulting to mocks keeps tests independent of collaborators' internals.
## Two kinds of Mockito-created object Both `mock()` and `spy()` return an instrumented object that records every call, so `verify()` works on either. What differs is what happens when a call arrives and no stubbing matches it — Mockito calls that the *default answer*. **Mock.** The default answer is `RETURNS_DEFAULTS`: the method body never runs and Mockito returns the zero value for the return type — `null` for objects, `0` for numerics, `false` for `boolean`, and empty collections/strings for a few container types (Mockito returns an empty list rather than null for `List`-returning methods, for instance). Nothing about the real class executes, so no field is touched, no exception is thrown from real logic, and constructors are not even called. **Spy.** The default answer is effectively "call the real method". You hand `spy()` an existing instance; Mockito builds an instrumented object of the same class, copies the original's fields into it, and lets unstubbed calls fall through to the genuine implementation running on that instrumented instance. ## Concretely ```java List<String> mocked = mock(ArrayList.class); mocked.add("a"); mocked.size(); // 0 — add() did nothing List<String> spied = spy(new ArrayList<String>()); spied.add("a"); spied.size(); // 1 — real behaviour verify(mocked).add("a"); // both record calls verify(spied).add("a"); ``` That `size() == 0` on the mock is the single most common beginner surprise: the object looks like a list, quacks like a list, and stores nothing. ## When each is right Use a **mock** for almost every collaborator. Unit tests want to control what a dependency returns and assert what was asked of it; running the dependency's real code defeats the isolation you were buying. A mocked `PaymentGateway` never touches the network; a mocked `Repo` never needs a database. Use a **spy** in two narrow situations: 1. **Real behaviour plus interaction assertions.** A small, side-effect-free helper whose real logic you want to keep, but where the test also needs to prove it was called with particular arguments. 2. **Legacy code you cannot restructure.** A class that does something untestable in one method — reads a file, calls a static, sleeps — where stubbing that one method is the cheapest way to get the rest under test until you can extract the dependency. Outside those, prefer the plain object. If you want a `PriceFormatter` to behave for real and you do not need to verify calls on it, just pass `new PriceFormatter()`. A spy adds instrumentation, cost, and a way for the test to get subtly wrong. ## Stubbing a spy is different The critical practical rule: because the real method runs, the usual `when(spy.method()).thenReturn(x)` form actually **invokes** `spy.method()` while you are setting up the stub. On `spy(new ArrayList<String>())`, writing `when(spied.get(0)).thenReturn("a")` throws `IndexOutOfBoundsException` from the real `get`. Use the `do*` family instead: ```java doReturn("a").when(spied).get(0); ``` That form never calls the real method. ## Other differences worth knowing - **Constructors.** `mock()` creates the instance without running any constructor; `spy(obj)` requires you to have built a real object first (or, with `@Spy` on an uninitialized field, lets Mockito call a no-arg constructor). - **The original is untouched.** `spy(list)` does not wrap `list`; it produces a separate instrumented instance with the fields copied across. Mutating the spy does not mutate the object you passed in. - **Argument capture and verification** behave identically on both. - **Strictness.** Mockito's strict stubbing flags unused stubs on both; a spy simply has fewer stubs to begin with. ## The mental one-liner A mock is a hollow shell that remembers what you asked it. A spy is the real object wearing a wire — it does its job, and it also reports everything it was asked to do.
- Can you verify() calls on a spy, or is verification only for mocks?Verification works identically on both. A spy is a Mockito-instrumented object that records every invocation before delegating to the real implementation, so verify(spy).add("a"), argument captors, and verifyNoMoreInteractions all apply. The only difference is what happens after recording: a spy runs the real code, a mock returns a default.
- You need a collaborator's real behaviour and you do not need to verify any calls on it. Should you use a spy?No — just pass the real object. A spy costs instrumentation and introduces stubbing pitfalls for no benefit if you are not stubbing or verifying anything. Reserve spies for the case where you genuinely need real behaviour plus interaction recording, or need to override exactly one method.
- Why does mock(ArrayList.class).size() return 0 even after add("a")?Because a mock replaces every method with a stub returning the type's default value; the real add never executes and no internal state changes. size() therefore returns the int default, 0. This is the intended isolation property of a mock, not a bug.
A mock is a cardboard cutout of a colleague; a spy is the real colleague with a microphone clipped on — they still do the work, and you also get a transcript.
saying these in an interview costs you the question
- Saying a spy wraps and delegates to the object you passed in — it is a separate copied instance
- Thinking spies cannot be verified because they run real code
- Expecting a mocked collection to actually store elements
- Using when(spy.method()) and being surprised when the real method executes during setup
- Reaching for a spy whenever a real object is needed, instead of just constructing it