Deep Analysis of Mocking vs Spying in Mockito: Evolution from callRealMethod to spy

Dec 04, 2025 · Programming · 11 views · 7.8

Keywords: Mockito | Unit Testing | Java Testing Framework

Abstract: This paper provides an in-depth exploration of the core differences between mocking and spying in the Mockito framework. By analyzing official documentation and best practices, it reveals spy as the recommended implementation for partial mocks, comparing it with callRealMethod usage scenarios. The article details differences in object construction, method invocation behavior, test code conciseness, and provides selection strategies for complex testing scenarios with practical code examples.

Technical Evolution and Design Philosophy

The evolution of the Mockito framework has witnessed significant design shifts in partial mock support. Prior to version 1.8.0, partial mocks were considered code smells, but through internal discussions and mailing list exchanges, framework developers eventually identified legitimate use cases and implemented support. This evolution reflects the maturation of test-driven development philosophy, where tools should adapt to practical development needs rather than impose restrictions.

Core Mechanism Comparison

Mocking and spying differ fundamentally in their underlying mechanisms. When creating a mock object using Mockito.mock(), the framework constructs a lightweight shell instance from the type's Class metadata, fully instrumented to track interactions but with no methods executing actual implementations by default. In contrast, Mockito.spy() wraps an existing real instance, maintaining original behavior while adding interaction tracking capabilities.

Consider the following comparative examples:

// Mock object example
@Test
public void testMockBehavior() {
    List mockedList = Mockito.mock(ArrayList.class);
    
    // Calls add method but doesn't actually add element
    mockedList.add("test");
    
    // Verifies method was invoked
    Mockito.verify(mockedList).add("test");
    
    // size() returns default value 0, not actual list size
    assertEquals(0, mockedList.size());
}
// Spy object example
@Test
public void testSpyBehavior() {
    // Creates spy based on real ArrayList instance
    List spyList = Mockito.spy(new ArrayList());
    
    // Calls actual add method to add element
    spyList.add("test");
    
    // Verifies method was invoked
    Mockito.verify(spyList).add("test");
    
    // size() calls real method, returns actual list size 1
    assertEquals(1, spyList.size());
}

Method Invocation Behavior Analysis

In method invocation handling, mock and spy objects follow different default rules. For mock objects, all methods are stubbed by default unless callRealMethod() is explicitly called. This means method invocations don't trigger actual implementations but return predefined responses or execute stubbed behavior.

Spy objects adopt the opposite default strategy: all methods call real implementations by default unless specific methods are explicitly stubbed. This design makes spy objects behave more closely to real objects while retaining selective overriding capability.

Code Conciseness and Maintainability

In practical testing scenarios, choosing between mock and spy directly impacts code conciseness and maintainability. Consider an object with 8 methods where a test needs to call 7 real methods and stub 1 method:

// Using mock approach
SomeClass mock = Mockito.mock(SomeClass.class);

// Need to call callRealMethod for 7 methods
Mockito.when(mock.method1()).thenCallRealMethod();
Mockito.when(mock.method2()).thenCallRealMethod();
// ... repeat 5 similar calls
Mockito.when(mock.method8()).thenReturn(stubbedValue);
// Using spy approach
SomeClass realInstance = new SomeClass();
SomeClass spy = Mockito.spy(realInstance);

// Only need to stub 1 method, other 7 maintain real behavior
Mockito.when(spy.method8()).thenReturn(stubbedValue);

The spy approach significantly reduces boilerplate code, improving test code readability and maintainability. Official documentation explicitly recommends spy() as the preferred way to create partial mocks, as it ensures real methods are called against correctly constructed objects.

API Design Differences and Compatibility

callRealMethod() was introduced after spy(), but spy() was retained to ensure backward compatibility. This design decision reflects cautious framework evolution. In API usage patterns, callRealMethod() allows continued use of traditional when().thenXxx() chaining, while spy objects may require doXxx().when() syntax in some cases to avoid unnecessary real method calls.

Practical Application Recommendations

When choosing between mock and spy, consider these factors:

  1. Object Construction Complexity: If object construction is complex or depends on specific state, using spy ensures testing in real environment
  2. Method Override Ratio: When the number of methods to override is much smaller than those to keep real, spy objects are more concise
  3. Test Isolation Requirements: Mock objects provide complete isolation, suitable for unit testing; spy objects retain partial real behavior, suitable for integration testing
  4. Code Readability: Spy objects make test intentions clearer, reducing repetitive callRealMethod() calls

Special attention is needed when handling text content containing HTML special characters, such as comparing the essential difference between <br> tags and newline characters \n. Strings in test code require proper escaping to avoid parsing errors. For example: String htmlContent = "Text contains <br> tag but doesn't break line";

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.