How do you create a Mockito spy, and what are the ways to do partial mocking on it?
answer
- Mockito.spy(realObject) or @Spy
- @Spy needs MockitoExtension / openMocks
- No-arg ctor used if no instance given
- doReturn/doNothing/doThrow for safe spy stubbing
- Spy copies state — use the spy, not the original
basics
~10 sCreate a spy with Mockito.spy(realObject) or the @Spy annotation (with @ExtendWith(MockitoExtension.class) or initMocks). Partial mocking means the spy runs real methods except the few you stub.
solid answer
~40 sThere are two main ways to make a spy. Programmatically: Spy<List> spy = Mockito.spy(new ArrayList<>()) — you pass a real instance to wrap. Declaratively: annotate a field with @Spy and enable Mockito (JUnit 5 @ExtendWith(MockitoExtension.class), or MockitoAnnotations.openMocks(this) in setup). With @Spy you can either provide an initialized instance (@Spy List<String> list = new ArrayList<>()) or let Mockito instantiate it via the no-arg constructor. Partial mocking is the spy's whole point: by default the real methods run, and you override only selected ones. To stub safely use doReturn(x).when(spy).method() or doNothing()/doThrow() for void methods, since when(spy.method()).thenReturn(x) would invoke the real method first. You can mix real and stubbed behavior freely; calls to non-stubbed methods still execute real code and are recorded for verification.
code
java · 17 lines@ExtendWith(MockitoExtension.class)
class DiscountTest {
@Spy
PriceCalculator calc = new PriceCalculator(); // real instance wrapped
@Test
void appliesStubbedTaxButRealRounding() {
// partial mock: override tax(), keep real round()
doReturn(0.0).when(calc).tax(anyDouble()); // safe stubbing
double total = calc.total(100.0); // real total() runs,
// calls stubbed tax() + real round()
assertEquals(100.0, total);
verify(calc).round(anyDouble()); // real call still recorded
}
}go deeper
Knows Mockito.spy(obj) and @Spy exist and that a spy wraps a real object. May not know the annotation wiring.
Creates spies both ways, wires MockitoExtension/openMocks, and uses doReturn().when() for partial mocking of selected methods.
Explains the state-copy semantics of spy(real), the no-arg-constructor requirement of @Spy, and the self-call caveat where internal calls bypass stubs.
Guides when partial mocking is acceptable vs a refactor signal, sets test conventions, and reasons about how spy semantics interact with @InjectMocks and constructor-injected designs.
## Goal: wrap a real object, override a little A **spy** is a test double that holds a **real instance** and runs its real methods by default, while letting you **override (stub) a few** and **verify** all calls. Overriding only part of an object's behavior is **partial mocking**. This section is the practical "how do I make one" companion to the conceptual mock-vs-spy distinction. ## Way 1 — programmatic: Mockito.spy(...) ```java import static org.mockito.Mockito.*; List<String> real = new ArrayList<>(); List<String> spy = Mockito.spy(real); ``` You must give `spy(...)` a **real object** (or a class — `spy(ArrayList.class)` instantiates one). Internally Mockito creates a proxy that **copies the state** of the passed object and delegates method calls to real implementations. Note it operates on a **copy**: mutating the original `real` afterward will not be seen by the spy, and vice versa. Always interact with the **spy**, not the original. ## Way 2 — declarative: @Spy ```java @ExtendWith(MockitoExtension.class) // JUnit 5 class OrderServiceTest { @Spy List<String> names = new ArrayList<>(); // explicit instance @Spy OrderValidator validator; // Mockito builds via no-arg ctor } ``` `@Spy` needs Mockito's annotation processing turned on: - **JUnit 5:** `@ExtendWith(MockitoExtension.class)` on the test class. - **JUnit 4:** `@RunWith(MockitoJUnitRunner.class)` or `MockitoAnnotations.openMocks(this)` in an `@Before` method (older code: `initMocks`). If you don't assign an instance, Mockito constructs one using the type's **no-argument constructor**; if there isn't one, it fails — so provide an instance for types without a no-arg constructor. `@Spy` also composes with `@InjectMocks`: a spied field can be injected into the class under test alongside `@Mock`s. ## Partial mocking — stubbing the slice you care about By default, calling any spy method runs **real code**. To override one: ```java doReturn("stubbed").when(spy).get(0); // safe: real get(0) is NOT called spy.add("x"); // real add runs String s = spy.get(0); // returns "stubbed" ``` For `void` methods that you don't want to actually execute: ```java doNothing().when(spy).expensiveLog(); // suppress the real void method doThrow(new RuntimeException()).when(spy).risky(); ``` Why the `do*().when(spy)` form? With `when(spy.get(0)).thenReturn("x")`, Java **evaluates `spy.get(0)` first**, invoking the **real** method (which on a fresh `ArrayList` throws `IndexOutOfBoundsException`). The `do*` family hands `when` the spy **without** calling the target method, so it's the safe idiom for spies and for any method with side effects. ## Verifying Everything you'd do with a mock works on a spy: ```java verify(spy).add("x"); verify(spy, times(2)).get(anyInt()); ``` Real calls are recorded too, so verification covers both real and stubbed invocations. ## Practical cautions - A spy's real methods can call **other real methods on the same object**; those internal calls also run real code, *not* your stubs (the stub only intercepts calls **through the spy reference**). This surprises people doing partial mocking of self-calling methods. - Heavy partial mocking is a **design smell** — it often means the class mixes responsibilities you can't easily isolate. Prefer extracting collaborators you can mock cleanly.
- You wrote @Spy on a field of a class with no no-arg constructor and didn't assign an instance. What happens?Mockito fails to create the spy because it tries to instantiate via the no-arg constructor. Fix it by assigning a constructed instance: @Spy Foo foo = new Foo(arg);.
- Inside a spy, methodA() calls this.methodB(). You stubbed methodB(). Does the stub apply during methodA()'s internal call?No — the internal this.methodB() call runs the real method, because the stub only intercepts calls made through the spy reference, not self-calls inside the real object.
saying these in an interview costs you the question
- Passing a class with no no-arg constructor to @Spy without an instance and expecting it to work.
- Mutating the original object after spy(real) and expecting the spy to reflect it — the spy works on a copy.
- Forgetting @ExtendWith(MockitoExtension.class) / openMocks, so @Spy fields stay null.
- Using when().thenReturn() to stub a spy method that has side effects or throws.