Spy and Real Objects
In unit testing, developers frequently encounter a scenario where the goal is to retain the genuine business logic of real objects while selectively monitoring or replacing certain methods. Mockito's Spy is purpose-built for this exact purpose. Unlike a completely fabricated mock, a spy wraps a real object and, by default, delegates all method invocations to the actual implementation. This article offers a systematic exploration of the relationship between spy and real objects, practical usage patterns, and common pitfalls encountered in test suites.
The distinction between a spy and a mock lies at the heart of Mockito's design philosophy. A mock is a wholly synthetic object constructed for testing; it carries no real business logic and relies on stubbed responses for every interaction. A spy, by contrast, is a wrapper around a genuine object. Its defining characteristic is the default delegation to real method implementations, while still retaining the ability to verify interactions and selectively stub specific methods.
This "partial mocking" capability makes spy an invaluable tool when testing legacy code or complex collaborator objects where fully replacing the collaborator would obscure the very behavior under test.
Creating a Spy: Two Primary Approaches
Mockito provides two principal ways to instantiate a spy, depending on whether you already have a test instance or need the framework to manage creation.
Way One: Wrapping an Existing Instance
The most direct method involves passing an already-initialized object to Mockito.spy(). This tells Mockito to wrap the existing instance rather than create a new one from scratch.
List<String> realList = new ArrayList<>();
List<String> spyList = Mockito.spy(realList);
Way Two: The @Spy Annotation
For cleaner test class setup, Mockito offers the @Spy annotation. When applied to a field, Mockito wraps the