@Mock vs @Spy Mockito | Java Fresher Interview
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
I'd say at-Mock creates a completely fake object where every method returns default values like null or zero unless you stub it.
It's like a hollow shell. At-Spy is different—it wraps a real object, so unstubbed methods actually run the real code. You only stub the methods you want to override.
Here's when you'd use each. If you're testing an OrderService that talks to a database repository, you'd mock the repository because you don't want real database calls in unit tests. The mock gives you full control—you tell it exactly what to return.
But let's say you have a PaymentService with ten methods, nine of them are simple calculations, and only one calls an external fraud detection API. You'd spy the PaymentService so the nine calculation methods run normally, and you'd stub only that one slow API call. The common mistake freshers make is using spy everywhere thinking it's safer because it runs real code.
But that defeats the purpose of unit testing. You end up with slow tests that hit real systems. Another gotcha is forgetting to initialize the spy with an actual object—at-Spy needs something real to wrap, like at-Spy private EmailValidator validator equals new EmailValidator.
Without that, you get null pointer exceptions. So the rule of thumb: mock your dependencies for isolation, spy only when you need partial mocking of the class under test itself.
@Mock vs @Spy Mockito | Java Fresher Interview
Direct Answer
@Mock creates a fully fake object where all methods return default values (null, 0, false) unless stubbed. @Spy wraps a real object instance where unstubbed methods call the real implementation; only configured methods are overridden. Use @Spy when you need partial mocking of a real object.
Code Example
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository mockRepo; // fully fake—no real DB calls
@Spy
private EmailValidator spyValidator = new EmailValidator(); // real object wrapped
@InjectMocks
private OrderService service;
@Test
void testWithMock() {
when(mockRepo.findById(1L)).thenReturn(Optional.of(new Order(1L, "Laptop")));
var order = service.getOrder(1L);
assertEquals("Laptop", order.item()); // mockRepo returned our stub
}
@Test
void testWithSpy() {
// spyValidator.isValid() runs real validation logic
// but we override formatEmail for this test
doReturn("FORMATTED@TEST.COM").when(spyValidator).formatEmail(anyString());
var result = service.validateAndFormat("test@test.com");
assertEquals("FORMATTED@TEST.COM", result); // overridden method
}
}
Common Mistakes & Pitfalls
- Using a spy as the default: unstubbed real methods may perform I/O, mutate state, or throw.
- Assuming
@Spyalways needs an initializer: Mockito can construct a suitable class, but explicit construction is clearer when chosen state or constructor arguments matter. - Eager stubbing on a spy:
when(spy.method())invokes the real method during setup; use ado...whenform when that call is unsafe. - Saying
doReturnis always required: ordinarywhencan work when the real setup call is harmless. - Spying the subject to avoid good design: extracting an external operation into a collaborator often gives a cleaner test.
Check your understanding
Answer these without looking back at the article.
- What is doReturn().when() and why do you use it with @Spy?
- When would you use a Spy over a Mock?
- How do you partially mock a class with Mockito?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.