Mock vs Spy in Mockito in JUnit: Key Differences and Usage
mock creates a fake object with no real behavior unless specified, while a spy wraps a real object allowing you to call real methods and selectively stub others. Use mock to isolate dependencies completely and spy when you want to partially mock an existing object.Quick Comparison
Here is a quick side-by-side comparison of mock and spy in Mockito:
| Factor | Mock | Spy |
|---|---|---|
| Object Type | Fake object with no real behavior | Wraps a real object |
| Method Calls | All methods return default or stubbed values | Calls real methods unless stubbed |
| Use Case | Isolate dependencies fully | Partial mocking of real objects |
| State Changes | No real state changes unless stubbed | Real state changes happen |
| Creation | Mockito.mock(Class.class) | Mockito.spy(realObject) |
| Performance | Usually faster, no real logic executed | Slower, real methods executed |
Key Differences
Mock creates a completely fake version of a class or interface. It does not execute any real code unless you explicitly tell it what to do using stubbing. This is useful when you want to isolate the unit under test from its dependencies and control all interactions.
On the other hand, spy wraps an existing real object. By default, it calls the real methods of that object. You can selectively override some methods with stubbing. This allows you to test parts of the real object while controlling or verifying others.
Because spy calls real methods, it can cause side effects or state changes in the wrapped object, which mock avoids. Therefore, spy is best when you want to test real behavior but still monitor or override some parts.
Code Comparison
import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; class MockExampleTest { @Test void testMockList() { List<String> mockedList = mock(ArrayList.class); // Stubbing size method when(mockedList.size()).thenReturn(5); // Calling size returns stubbed value assertEquals(5, mockedList.size()); // Calling add does nothing real mockedList.add("one"); assertEquals(5, mockedList.size()); // still 5, no real add // Verify add was called verify(mockedList).add("one"); } }
Spy Equivalent
import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; class SpyExampleTest { @Test void testSpyList() { List<String> realList = new ArrayList<>(); List<String> spyList = spy(realList); // By default, calls real method spyList.add("one"); assertEquals(1, spyList.size()); // real size is 1 // Stub size method when(spyList.size()).thenReturn(5); assertEquals(5, spyList.size()); // stubbed value // Verify add was called verify(spyList).add("one"); } }
When to Use Which
Choose mock when: You want to isolate the unit under test completely from its dependencies and control all interactions without executing any real code. This is ideal for testing behavior without side effects.
Choose spy when: You want to test a real object but override or verify some methods selectively. This is useful for partial mocking when you want to keep real behavior but control or observe specific parts.
In summary, use mock for full fake objects and spy for partial real objects.
Key Takeaways
mock to create fully fake objects with no real method execution.spy to wrap real objects and call real methods unless stubbed.