What is the difference between @Mock and @Spy, and how do they each behave when injected via @InjectMocks?
answer
- @Mock: all methods default until stubbed
- @Spy: real object, unstubbed methods run real code
- Spy stubbing trap: use doReturn().when(spy) not when(spy...)
- @InjectMocks injects spies and mocks the same way
- Spying the SUT itself = smell
basics
~20 sA @Mock is a complete fake — every method returns a default until you stub it. A @Spy wraps a real object and calls real methods by default, letting you override just some. @InjectMocks can inject either into the object under test.
solid answer
~40 s@Mock creates a full stand-in: all methods return default values (null/0/false/empty) until stubbed, and no real code runs. @Spy wraps a real instance (a partial mock): unstubbed methods execute the real implementation, and you override only the ones you choose. @InjectMocks injects both @Mock and @Spy fields into the SUT by the same type-then-name rules. A key gotcha with spies is stubbing: writing when(spy.method()).thenReturn(x) actually calls the real method during stubbing, which can have side effects or throw; the safe form is doReturn(x).when(spy).method(). Spies are useful when you want mostly-real behaviour but need to intercept a method or two — e.g. a real object with one expensive call stubbed. But heavy reliance on spies usually signals the class under test should be split so its parts can be mocked cleanly.
go deeper
Knows @Mock is a full fake and @Spy wraps a real object that runs real methods unless stubbed.
Explains default-value behaviour, the doReturn vs when stubbing trap for spies, and that @InjectMocks injects both the same way.
Chooses spy vs mock deliberately, recognizes spying the SUT as a smell, and knows partial-mock pitfalls with side effects.
Sets guidance that spies are for collaborators not the SUT, drives extraction of testable seams so plain mocks suffice, and reviews tests for spy overuse.
## Two kinds of test double Mockito offers two field annotations for substituting collaborators, and they differ in **default behaviour**. ### @Mock — a full fake ```java @Mock UserRepository repo; ``` Every method is replaced. Until you stub it, each returns a **default**: `null` for object types, `0`/`0.0` for numbers, `false` for booleans, and an empty collection/stream/Optional for those return types. No real logic runs. This is the normal choice for isolating the SUT from its collaborators. ### @Spy — a partial mock wrapping a real object ```java @Spy List<String> list = new ArrayList<>(); ``` A spy holds a **real** instance. Unstubbed methods run the **real** implementation; you override only the specific methods you stub. So a spied `ArrayList` actually stores elements unless you stub `add`. Spies are for the case "I want the real object, but with one or two methods intercepted." ## The spy stubbing trap With a mock, `when(mock.foo()).thenReturn(x)` is safe because `mock.foo()` does nothing. With a **spy**, `when(spy.foo()).thenReturn(x)` first **actually invokes** the real `spy.foo()` to evaluate the argument to `when(...)` — which may run expensive code, mutate state, or throw. The correct idiom for spies is: ```java doReturn(x).when(spy).foo(); // does NOT call the real foo() ``` The `doReturn(...).when(...)` family avoids the real call. This is the single most common spy mistake. ## Behaviour under @InjectMocks `@InjectMocks` injects `@Mock`, `@Spy`, and `@Captor`-independent fields alike, using the **same resolution** (constructor → setter → field; type then name). A spy is injected just like a mock; the difference is purely in how the injected double behaves when its methods are called. So you can inject a spy of a collaborator into the SUT to keep most of that collaborator real while overriding a slice of it. ## When to use which - **@Mock** — the default. You don't care about the collaborator's real behaviour, only how the SUT interacts with it. - **@Spy** — when you genuinely need the real behaviour of an object but must intercept a method (e.g. avoid one slow/external call, or assert-and-delegate). Also used for wrapping a real object you don't own. ## Design caution Reaching for `@Spy` on the **class under test itself** (to stub one of its own methods) is usually a smell: it means you're testing a class while faking part of it, which hides coupling. The cleaner fix is to extract the stubbed behaviour into a separate collaborator that can be a plain `@Mock`. Spies are best reserved for collaborators, not the SUT. ## Quick comparison | Aspect | @Mock | @Spy | |---|---|---| | Backing object | none (pure fake) | a real instance | | Unstubbed method | returns default | runs real code | | Safe stubbing | when(...).thenReturn | doReturn(...).when(...) | | Typical use | isolate collaborator | mostly-real with overrides |
- Why can when(spy.foo()).thenReturn(x) be dangerous?It evaluates spy.foo() for real before stubbing, so the real method actually runs — possibly with side effects or exceptions. Use doReturn(x).when(spy).foo() to avoid the real call.
- Does @InjectMocks inject a @Spy differently from a @Mock?No — injection uses the same constructor→setter→field, type-then-name resolution for both. Only the runtime behaviour of the injected double differs.
saying these in an interview costs you the question
- Thinking a @Spy returns defaults like a mock by default
- Using when(spy.method()).thenReturn(...) and being surprised the real method ran
- Believing @InjectMocks treats spies differently from mocks during injection
- Routinely spying the class under test instead of extracting a collaborator