Mockito Mock Vs. Spy: Differences & The Right Approach

Understand the differences between Mockito Mock and Spy, their use cases, and the right approach for effective unit testing.

Written by Sourabh G Sourabh G
Reviewed by Siddhi Rao Siddhi Rao
Last updated: 28 July 2026 10 min read

Key Takeaways

  • Mockito Mock creates a test double with no real behavior, making it useful when you want to isolate the class under test from its dependencies.
  • Mockito Spy wraps a real object, allowing you to execute actual methods while overriding only the behavior you need for a specific test.
  • Choosing between a mock and a spy depends on how much of the original implementation your test should exercise to stay both reliable and easy to maintain.

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:

AspectMockito MockMockito Spy
DefinitionCreates 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.
BehaviorAnalyzes the behavior of a class. All methods return default values unless specified.Executes real methods unless explicitly mocked.
Use CaseIdeal for isolating tests by managing dependencies.Ideal for testing partial behaviors where some methods are real, and others are mocked.
StubbingAlways need explicit stubbing for method behavior.Allows partial stubbing along with real method calls.
Object TypeCan mock both interfaces and concrete classes.Can spy on both interfaces and concrete classes.
Default BehaviorReturns default values like null, 0, or false unless specified.Executes the real method unless explicitly mocked.
Created byCreated 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().

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:

ApproachChallengeWhat It Means
MockOver-mockingCreating too many mocks can make tests tightly coupled to the implementation instead of the behavior being tested.
MockExtensive setupComplex dependencies may require a lot of stubbing before the test can run.
MockUnrealistic behaviorSince mocks don’t execute real code, they may miss issues that only appear with actual implementations.
SpyReal methods execute by defaultUnless stubbed, spies call the original methods, which can trigger unintended side effects.
SpyHarder to maintainPartial mocking can make it less obvious which methods are real and which are stubbed.
SpySlower testsExecuting real methods may increase execution time compared to pure mocks.

Best Practices:

RecommendationWhy 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.

Talk to an Expert

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.

Version History

  1. Jul 28, 2026 Current Version

    Revamped difference sections with clear comparison tables and added new sub-sections on how each works.

    Siddhi Rao
    Reviewed by Siddhi Rao Lead Customer Engineer
Tags
Automation Testing Real Device Cloud
Sourabh G
Sourabh G

Senior Software Engineer

Sourabh Gome is a Senior Software Engineer with 5+ years of experience building scalable, high-performance software systems. He specializes in test automation, quality engineering, and developer productivity, helping teams deliver reliable applications with greater speed and confidence.

Automation Tests on Real Devices & Browsers
Seamlessly Run Automation Tests on 3500+ real Devices & Browsers