When I write unit tests, one of the first decisions I make is whether to use a mock or a spy. Both help isolate the code I’m testing, but they solve different problems. A mock replaces a dependency completely, while a spy lets me work with a real object and override only the parts that matter for the test.
Both are provided by Mockito, one of the most widely used Java libraries for unit testing. Knowing when to use each can make your tests easier to understand, less brittle, and more focused on the behavior you’re trying to verify.
In this guide, I’ll compare Mockito Mock and Spy, explain how they work, highlight their differences, and show when each approach makes the most sense.
Comparison Table
Here are some of the major differences between Mockito Mock and Spy:
| Aspect | Mockito Mock | Mockito Spy |
|---|---|---|
| Definition | Creates a mock object of a class, which does not have real behavior unless explicitly defined. | Wraps an existing object, allowing both real and mocked behavior. |
| Behavior | Analyzes the behavior of a class. All methods return default values unless specified. | Executes real methods unless explicitly mocked. |
| Use Case | Ideal for isolating tests by managing dependencies. | Ideal for testing partial behaviors where some methods are real, and others are mocked. |
| Stubbing | Always need explicit stubbing for method behavior. | Allows partial stubbing along with real method calls. |
| Object Type | Can mock both interfaces and concrete classes. | Can spy on both interfaces and concrete classes. |
| Default Behavior | Returns default values like null, 0, or false unless specified. | Executes the real method unless explicitly mocked. |
| Created by | Created using Mockito.mock(Class<T>.class). | Created using Mockito.spy(Object). |
What is Mockito Mock?
A Mockito Mock is a test double that replaces a real object during a unit test. Instead of executing the object’s actual methods, you decide how it should respond when a method is called. This lets you test a class in isolation without depending on databases, APIs, file systems, or other external components.
You can also verify how the mock was used, such as whether a method was called, how many times it was invoked, or which arguments were passed. This helps confirm that your code interacts with its dependencies in the way you expect.
Syntax and Example
First, create a mock of the dependency, define how it should behave, pass it to the class under test, and then verify the result.
import static org.mockito.Mockito.*;
public class UserServiceTest {
@Test
public void testGetUser() {
// Create a mock of the UserRepository class
UserRepository userRepository = mock(UserRepository.class);
when(userRepository.getUserById(1))
.thenReturn(new User(1, "Ayush Singh"));
UserService userService = new UserService(userRepository);
User user = userService.getUser(1);
assertEquals("Ayush Singh", user.getName());
}
}In this example, UserRepository is never called for real. Instead, the mock returns the User object configured with thenReturn(). This lets the test focus entirely on the logic inside UserService without depending on a database or any external resource.
Why Use a Mock?
Mocks are useful when you want complete control over a dependency during a test. They help you:
- Isolate the class under test: Replace databases, APIs, or other services so you’re testing only the class you’re interested in.
- Control different scenarios: Return specific values, throw exceptions, or simulate edge cases without changing the real implementation.
- Run tests faster: Since no external systems are involved, unit tests execute quickly and consistently.
- Verify interactions: Confirm that a dependency was called with the expected arguments and the correct number of times.
How Mockito Mocks Work
When you create a mock, Mockito generates a fake implementation of the class or interface. By default, its methods return standard values such as null, 0, or false.
You can override this behavior using when(…).thenReturn(…) or similar stubbing methods. After running your test, you can use verify() to check whether the expected interactions actually happened.
This usually comes down to four steps:
- Create a mock with Mockito.mock().
- Define its behavior with when(…).thenReturn(…).
- Execute the code you’re testing.
- Verify the result or confirm the expected interactions with verify().
Learn More: What is Android Unit Testing?
What is Mockito Spy?
A Mockito Spy wraps a real object instead of replacing it. By default, the object’s actual methods continue to run, but you can override selected methods when you need different behavior for a test.
This is useful when you want to preserve most of an object’s implementation while controlling only a few methods that are difficult to test or depend on external behavior.
Syntax and Example
To create a spy, start with a real object and wrap it using Mockito.spy(). You can then stub individual methods while leaving the rest of the object unchanged.
import static org.mockito.Mockito.*;
public class UserServiceTest {
@Test
public void testSaveUser() {
UserRepository realUserRepository = new UserRepository();
UserRepository spyRepository = spy(realUserRepository);
doReturn(false).when(spyRepository).save(any(User.class));
UserService userService = new UserService(spyRepository);
boolean result = userService.saveUser(new User(1, "Ayush Singh"));
assertFalse(result);
}
}In this example, spyRepository behaves like a real UserRepository, except for the save() method. That method is stubbed to return false, while every other method continues to execute its original implementation.
Why Use a Spy?
A spy is useful when replacing the entire object with a mock would hide behavior you actually want to test. It lets you keep the real implementation while modifying only the parts that matter. You might choose a spy when you need to:
- Partially override behavior: Stub only specific methods while allowing the rest of the object to execute normally.
- Test real implementation: Verify how the actual object behaves without rewriting every dependency as a mock.
- Observe interactions: Confirm that real methods were called as expected during a larger workflow.
- Reduce test setup: Reuse an existing object instead of configuring every method individually.
How Mockito Spies Work
Unlike a mock, a spy delegates method calls to the real object unless you explicitly stub them. That means most methods execute exactly as they would in production, making spies useful for partial mocking.
A typical workflow looks like this:
- Create the real object.
- Wrap it with Mockito.spy().
- Override only the methods you want using doReturn(…).when(…).
- Execute the test and use verify() if you want to confirm specific interactions.
Choosing Between a Mock and a Spy
The biggest difference is how much of the real object you want to keep during a test. A mock replaces the object completely, while a spy wraps a real object and lets you override only selected methods.
Here’s a simple example that shows both approaches:
import static org.mockito.Mockito.*;
public class ServiceTest {
@Test
public void testSpyVsMock() {
// Using Mock
List<String> mockList = mock(List.class);
when(mockList.size()).thenReturn(5);
assertEquals(5, mockList.size());
// Using Spy
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
when(spyList.size()).thenReturn(5);
assertEquals(5, spyList.size());
spyList.add("Item");
verify(spyList).add("Item");
}
}Although both tests return 5 for size(), they behave differently. The mock never calls the real implementation, whereas the spy still works with the underlying ArrayList. That’s why you can verify that “Item” was actually added to the list.
Which One Should You Use?
- Choose a mock when you want to isolate the class under test completely. This is usually the better option for dependencies such as databases, REST clients, or messaging services where running real code would make tests slower or harder to control.
- Choose a spy when you want to keep most of an object’s real behavior but override a few methods. This works well for legacy code, utility classes, or objects with complex logic where replacing everything with a mock would remove the behavior you’re actually trying to test.
As a general rule, I start with a mock because it keeps unit tests focused and predictable. I switch to a spy only when I need part of the real implementation to execute and mocking the entire object would hide important behavior.
Common Challenges and Best Practices
I’ve come to appreciate both, but I’ve also learned each has its trade-offs. Once you understand what you’re actually giving up with either approach, you can make a call that keeps your tests easier to maintain down the line.
Common Challenges:
| Approach | Challenge | What It Means |
|---|---|---|
| Mock | Over-mocking | Creating too many mocks can make tests tightly coupled to the implementation instead of the behavior being tested. |
| Mock | Extensive setup | Complex dependencies may require a lot of stubbing before the test can run. |
| Mock | Unrealistic behavior | Since mocks don’t execute real code, they may miss issues that only appear with actual implementations. |
| Spy | Real methods execute by default | Unless stubbed, spies call the original methods, which can trigger unintended side effects. |
| Spy | Harder to maintain | Partial mocking can make it less obvious which methods are real and which are stubbed. |
| Spy | Slower tests | Executing real methods may increase execution time compared to pure mocks. |
Best Practices:
| Recommendation | Why It Helps |
|---|---|
| Start with mocks whenever possible. | Keeping dependencies isolated usually results in simpler, more predictable unit tests. |
| Use spies only when partial mocking is genuinely required. | If you only need to override one or two methods, a spy is often a better fit than recreating the entire object. |
| Stub only the methods your test depends on. | Minimal stubbing keeps tests easier to read and reduces unnecessary setup. |
| Verify meaningful interactions instead of every method call. | Focus on the behavior that matters rather than implementation details that may change over time. |
| Keep the test setup as small as possible. | Simpler tests are easier to understand, maintain, and debug when they fail. |
Conclusion
Mockito Mock and Spy solve different testing problems, so neither is inherently better than the other. A mock is the right choice when you want to isolate the class under test and fully control its dependencies. A spy makes more sense when you need most of a real object’s behavior but want to override a few methods for specific scenarios.
If you’re unsure which one to use, start with a mock. It’s simpler, keeps unit tests focused, and avoids unintended side effects. Switching to a spy only when using the real implementation adds value to the test. Choosing the right approach helps keep your tests easier to read, maintain, and trust as your codebase grows.