How does Mockito create the object when you annotate a test-class field with @Spy, and what kinds of classes or methods will that fail on?
answer
- Initialized field → that instance is spied
- Uninitialized → no-arg constructor, private allowed
- No no-arg constructor → initialize inline
- No interfaces / nothing abstract to instantiate
- private & static never intercepted; final depends on mock maker
basics
~20 sIf you initialize the field yourself, Mockito spies that instance. If you leave it uninitialized, Mockito instantiates the type using a no-arg constructor (private is fine) and spies that. It fails for interfaces and abstract types with no instance, and for types with only argument-taking constructors — initialize those inline.
solid answer
~50 s`@Spy` has two modes. **Field initialized inline** — `@Spy PriceFormatter formatter = new PriceFormatter(Locale.UK);` — Mockito spies exactly that object: it builds an instrumented instance of the same class and copies the fields across. Use this whenever construction needs arguments or specific state. **Field left uninitialized** — `@Spy PriceFormatter formatter;` — Mockito instantiates the type itself, looking for a **no-arg constructor** (it may be private). For a non-static inner class it uses the outer instance. If no no-arg constructor exists, annotation processing fails with a message telling you to provide an instance. What it cannot do: spy an interface or abstract class with nothing to instantiate; intercept `private` or `static` methods in any version. With Mockito 5's default inline mock maker, `final` classes and `final` methods *can* be spied; on Mockito 2–4's subclass mock maker they could not, and stubbing a final method silently did nothing. As always, annotations do nothing without `MockitoExtension`, `openMocks`, or the JUnit 4 runner.
code
java · 15 lines@ExtendWith(MockitoExtension.class)
class PricingTest {
@Spy PriceFormatter formatter = new PriceFormatter(Locale.UK); // needs an argument -> inline
@Spy AuditLog auditLog; // has a no-arg constructor
@Mock Repo repo;
@InjectMocks PricingService service; // spies are injected like mocks
@Test
void usesRealFormattingButRecordsTheCall() {
service.quote(new Item("book"));
verify(formatter).format(any(BigDecimal.class));
}
}go deeper
Know the two forms — initialize inline, or let Mockito use a no-arg constructor — and that you cannot spy an interface.
Add the failure cases and the interception limits: private and static are never stubbable, final depends on the mock maker.
Tie the final-method behaviour to the Mockito version and mock-maker choice, and diagnose a silently ignored stub from that.
Consider it at dependency-policy level: which mock maker the codebase standardises on, and whether tests should be permitted to depend on instrumenting final or legacy types at all.
## Two ways the field gets its value **You supply the instance.** ```java @Spy PriceFormatter formatter = new PriceFormatter(Locale.UK); ``` Mockito sees the field already initialized and spies that object — equivalent to `spy(new PriceFormatter(Locale.UK))`. This is the form to prefer whenever the object needs constructor arguments, needs to start in a particular state, or is expensive enough that you want to control its construction explicitly. It is also more readable: the reader sees what is being spied. **Mockito supplies the instance.** ```java @Spy PriceFormatter formatter; ``` With the field null, Mockito instantiates the declared type by locating a **no-arg constructor** and invoking it reflectively. Visibility is not a barrier — a `private` no-arg constructor works. For a non-static inner class, Mockito uses the enclosing test instance as the outer object. If no no-arg constructor is available, annotation processing throws with a message asking you to provide the instance yourself; that error is the direct cue to switch to the inline form. Either way the outcome is a spy, so unstubbed calls run real code, stubbed calls do not, and every call is recorded for `verify()`. ## What it cannot instantiate - **Interfaces.** There is nothing to instantiate and no real behaviour to run. If you want default methods exercised, spy a concrete implementation or use `mock(Iface.class, CALLS_REAL_METHODS)` for a targeted case. - **Abstract classes** with no supplied instance, for the same reason. (`mock(Abstract.class, CALLS_REAL_METHODS)` is the usual workaround when you need the concrete methods of an abstract base.) - **Types with only argument-taking constructors.** Initialize inline. - **Types whose no-arg constructor throws or does heavy work.** It will run for real during setup — a good reason to prefer the explicit form. ## What it cannot intercept Regardless of how the instance was obtained, some methods are outside Mockito's reach: - **`private` methods** — never overridable, never intercepted, in any Mockito version. Calls to them go straight to the real code. - **`static` methods** — need `Mockito.mockStatic(...)` in a try-with-resources scope; a spy has no effect on them. - **`final` methods and `final` classes** — this one is version-dependent. Mockito 2 through 4 defaulted to the subclass (Byte Buddy) mock maker, which physically cannot override `final`, so a final class could not be spied at all and a stub on a final method was silently ignored: the real method ran, and the test failed for reasons that looked impossible. Mockito 5 switched the default to the **inline mock maker**, which instruments loaded classes via a Java agent and can handle final classes and methods. If you are on an older version, `mockito-inline` as a dependency gives you the same capability. A related consequence: because a spy is an instrumented instance created without running a constructor and then field-copied, `equals` and `hashCode` are not intercepted by Mockito either — Mockito needs them for its own bookkeeping. Do not attempt to stub them. ## Interaction with @InjectMocks `@Spy` fields participate in injection exactly like `@Mock` fields: they sit in the same candidate pool and are matched by type, then by field name. So a spied collaborator can be handed to the object under test without any extra wiring. Stacking `@Spy` on the *same* field as `@InjectMocks` is a different feature — it wires the object under test and then wraps it as a partial mock. ## Nothing runs without annotation processing `@Spy`, like every Mockito annotation, is inert metadata. Use `@ExtendWith(MockitoExtension.class)` on JUnit 5, `MockitoAnnotations.openMocks(this)` in a `@BeforeEach` (closing the returned `AutoCloseable` afterwards), or the JUnit 4 runner/rule. Without one, the field is whatever your initializer left there — a **plain, uninstrumented object** if you initialized it inline, which is the nastiest variant of the mistake: the test runs, real behaviour happens, and `verify()` then fails with `NotAMockException` from a line that looks correct. ## Practical defaults - Initialize inline whenever construction is not trivially no-arg; it removes a class of setup failures and reads better. - Do not spy interfaces — spy the implementation. - On Mockito 5, final is no longer an obstacle; on older versions, check the mock maker before blaming your test. - Keep spies rare. `@Spy` on a collaborator you neither stub nor verify should just be a plain field holding a real object.
- @Spy on a class whose only constructor takes arguments — what happens, and what is the fix?Annotation processing fails, because Mockito looks for a no-arg constructor to instantiate the field and finds none. The fix is to initialize the field inline — @Spy Foo foo = new Foo(arg) — so Mockito spies the instance you built rather than trying to create one.
- Why might a stub on a spy appear to be ignored, with the real method running instead?The usual cause is that the method is final or private. Private methods are never intercepted in any Mockito version. Final methods could not be intercepted by the subclass mock maker that Mockito 2 through 4 used by default, so the stub was silently dropped; Mockito 5's inline mock maker handles them. Check the Mockito version and the method modifiers before suspecting anything else.
saying these in an interview costs you the question
- Thinking @Spy can instantiate a class that has only argument-taking constructors
- Claiming Mockito cannot use a private no-arg constructor
- Trying to @Spy an interface and expecting real behaviour
- Assuming final methods are stubbable on every Mockito version
- Forgetting that without MockitoExtension or openMocks an inline-initialized @Spy field is just a plain object