In a Mockito test, is a test-class field annotated @Spy injected into the object annotated @InjectMocks? And what does it mean to put both @Spy and @InjectMocks on the same field?
answer
- @Mock and @Spy share one injection pool
- @Spy + @InjectMocks = wire it, then wrap it
- Partial mock of the class under test
- doReturn().when(spy) — never when(spy.method())
- Self-calls hit the stub, so refactors break it
basics
~20 sYes — @Spy fields join @Mock fields in the injection pool, so a spied collaborator is injected like any mock. Putting @Spy and @InjectMocks on the same field means the object under test is itself wrapped as a spy after its dependencies are injected, making it a partial mock.
solid answer
~50 sTwo separate questions. **A `@Spy` collaborator is injectable.** Mockito's candidate pool is every `@Mock` *and* `@Spy` field of the test class, so `@Spy Validator validator;` is matched by type and name exactly like a mock and handed to the object under test. The difference is only in the double's behaviour — unstubbed calls run the real code. **Both annotations on one field** — `@Spy @InjectMocks OrderService service;` — means: build the object, inject its dependencies as usual, then wrap the result as a spy. Now you can stub or verify the object under test's *own* methods while the rest of it stays real. That combination is legal and occasionally useful (stubbing an awkward protected hook, or a legacy class you cannot yet split), but it is a partial mock of the class under test, and most teams treat it as a smell: you are faking part of the very thing the test claims to verify. Prefer extracting the stubbed behaviour into a real collaborator you can mock outright.
code
java · 12 lines@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock Repo repo;
@Spy PriceFormatter formatter = new PriceFormatter(Locale.UK); // real behaviour, still verifiable
@InjectMocks OrderService service;
@Test
void formatsTotal() {
service.summarize(new Order("book", 2));
verify(formatter).format(any(BigDecimal.class));
}
}go deeper
Know that @Spy collaborators are injected just like @Mock ones, and that a spy runs real code unless stubbed.
Explain what @Spy + @InjectMocks builds — wired object then wrapped as a spy — and use the doReturn stubbing form.
Argue the tradeoff: partial mocking the class under test hides behaviour from the test and couples it to internal call structure; propose extracting the awkward dependency instead.
Treat prevalence of @Spy @InjectMocks in a suite as a design signal about class responsibilities, and decide when legacy or template-method cases justify a deliberate exception.
## Part one: @Spy fields are injection candidates When Mockito processes annotations it collects the test class's `@Mock` and `@Spy` fields into a single pool of injection candidates. Nothing about the injection algorithm treats a spy differently: it is filtered by type, disambiguated by field name if several candidates match, and passed through the constructor, a setter, or a direct field write like anything else. So this works exactly as you would hope: ```java @Mock Repo repo; @Spy PriceFormatter formatter = new PriceFormatter(Locale.UK); @InjectMocks OrderService service; ``` `service` receives the real-behaving `formatter`. Unstubbed calls on it execute the genuine formatting logic; you can still `verify(formatter).format(...)` afterwards, and you can override one method with `doReturn(...).when(formatter).format(...)` if a particular case is awkward. The legitimate use is a collaborator whose real behaviour you *want* in the test — a pure value-mapping class, a formatter, a small in-memory helper — but whose interactions you still want to assert, or one of whose methods you need to force. If you neither need real behaviour nor interaction recording, use `@Mock`; if you only need real behaviour, just construct the object and inject it manually. ## Part two: @Spy and @InjectMocks on the same field Stacking both annotations on the field under test is a distinct feature. The sequence is: 1. Mockito instantiates the type (constructor injection with the biggest constructor, as usual) and injects the mock/spy collaborators. 2. It then wraps that fully wired instance as a spy. The result is a **partial mock of the class under test**: unstubbed methods run the real implementation, but you may stub individual methods and verify calls on the object itself. Syntax matters here. Because the object is real, `when(service.loadConfig()).thenReturn(cfg)` would call `loadConfig()` for real while setting up the stub — with all its side effects. Always use the `doReturn(...).when(spy).method()` form on spies: ```java doReturn(cfg).when(service).loadConfig(); ``` A further subtlety: self-invocation *is* intercepted. Mockito's spy is a generated subclass instance with the original's fields copied into it, and the real method bodies run on that instance, so an internal call from `placeOrder()` to `this.loadConfig()` hits the stub. This is exactly why the technique works at all — and also why it is easy to over-use. ## Why teams push back on it The test's claim is "the class under test behaves correctly". A partial mock weakens that claim: whatever you stubbed is no longer under test, and if the real method changes, the stub does not, so the test keeps passing against behaviour that no longer exists. The stub also encodes an assumption about the class's *internal* call structure, so an innocuous refactor — inlining the stubbed method, renaming it, making it private — breaks the test for reasons unrelated to behaviour. A further limitation with Mockito's default subclass instrumentation on older versions: `private` methods can never be stubbed, and `final` methods could not be either. Mockito 5 makes the inline mock maker the default, which lifts the `final` restriction, but `private` remains untouchable and depending on the mechanism is fragile either way. ## What to do instead When you feel the pull toward `@Spy @InjectMocks`, the underlying signal is usually that the class does two things: the behaviour you want to test, and an awkward dependency (time, randomness, I/O, a static lookup) baked into one of its own methods. Extract that awkward part into a collaborator — a `Clock`, an `IdGenerator`, a `ConfigLoader` — inject it, and mock it with a plain `@Mock`. The test then verifies the whole class, the design improves, and the stub no longer depends on private call structure. The honest exceptions: legacy classes you cannot restructure yet, and template-method style base classes where a protected hook is genuinely part of the extension contract. In those cases use the combination deliberately, keep the number of stubbed self-methods to one, and leave a comment saying why. ## Quick reference - `@Spy` alone on a collaborator: real behaviour, still verifiable, injected like a mock. - `@Spy` + `@InjectMocks` on the object under test: partial mock of the thing you are testing — use sparingly. - Stub spies with `doReturn/doThrow/doAnswer ... .when(spy).method()`, never `when(spy.method())`. - Never stub the method the test is actually about.
- Why must you stub a spy with doReturn(...).when(spy).method() instead of when(spy.method())?Because the spy runs real code. Writing when(spy.method()) evaluates spy.method() for real while recording the stub, so any side effect or exception in that method happens during setup — a real problem for methods that touch state, throw on empty input, or are expensive. The doReturn form never invokes the real method.
- If a spy is a copy of the original object, does stubbing a method affect calls that the object makes on itself?Yes. The spy is a generated subclass instance holding copies of the original's fields, and the real method bodies execute on that instance, so `this` refers to the spy. An internal call from one method to another is intercepted and sees the stub. That is what makes @Spy @InjectMocks usable at all — and also why stubbing self-calls couples the test to the class's internal call structure.
saying these in an interview costs you the question
- Claiming @Spy fields are not injected into @InjectMocks objects
- Using when(spy.method()) on a spy and being surprised by real side effects
- Stubbing the very method the test is supposed to verify
- Reaching for @Spy @InjectMocks as the default instead of extracting a collaborator
- Assuming private methods can be stubbed on a spy