Mockito
mocking
spying
Java testing
unit testing

What is the difference between mocking and spying when using Mockito?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

In the realm of unit testing in Java, Mockito stands out as a popular framework that allows developers to write clean and effective tests. Among the capabilities of Mockito, two of its most significant features are mocking and spying. Both functionalities serve the purpose of simulating the behavior of classes to isolate system under test (SUT) from its dependencies. Despite their similarities, mocking and spying are distinct concepts with different use cases.

Mocking

Mocking is the process of creating an object that mimics the behavior of a real object. It allows developers to define the specific responses of the mocked methods without depending on the actual implementation. This is especially beneficial when the actual method involves complex operations, risky side effects, or dependencies that are challenging to set up or control.

Technical Explanation and Example

In Mockito, when you create a mock, you are essentially creating a skeleton that does nothing unless explicitly instructed. Let's look at a simple example:

java
1// Import Mockito library
2import static org.mockito.Mockito.*;
3
4// Create a mock of the class
5List<String> mockedList = mock(List.class);
6
7// Define the behavior of the mock object
8when(mockedList.get(0)).thenReturn("Mockito");
9
10// Using the mock object
11System.out.println(mockedList.get(0)); // Outputs "Mockito"
12System.out.println(mockedList.size()); // Outputs "0"

In the above example, mockedList is a mock object of the List interface. We define that when the get(0) method is called, it should return "Mockito". Other methods, like size(), return default values (e.g., 0 for integers).

Characteristics of Mocking

  1. Default Behavior: Methods of the mocked object return default values (zero, null, or false) unless explicitly specified.
  2. Dependency Isolation: Best used when the method under test interacts with external systems or requires complex setup.
  3. Control: You have complete control over how the mock behaves through configurations.

Spying

Spying, on the other hand, is a technique that allows you to create a partial mock. Unlike mocking, spying involves wrapping a real instance of a class and often is used when you want to use the actual implementation of the class while still verifying method calls or overriding specific behavior.

Technical Explanation and Example

A spy is a real object, but you can modify its behavior as needed. Here's an example:

java
1// Import Mockito library
2import static org.mockito.Mockito.*;
3
4// Create a real object
5List<String> list = new ArrayList<>();
6list.add("Real Data");
7
8// Create a spy of the list
9List<String> spyList = spy(list);
10
11// Optionally, stub method calls on the spy
12when(spyList.size()).thenReturn(100);
13
14// Using the spy object
15System.out.println(spyList.get(0)); // Outputs "Real Data"
16System.out.println(spyList.size()); // Outputs "100"

In the example above, spyList is a spy of list. The actual list is used, but we can still override the behavior of methods. For example, size() returns 100 instead of the actual size due to behavior stubbing.

Characteristics of Spying

  1. Real Behavior: By default, a spy uses the real object’s code, rather than a default value.
  2. Overridable: Specific behaviors or method calls can be overridden as needed.
  3. Verification: You can verify that certain methods were called on the spy using Mockito’s verification features.

Key Differences

Here is a concise summary in table format of the key differences between mocking and spying in Mockito:

FeatureMockingSpying
Object BehaviorDoes not invoke real methodBy default, uses real methods
Method StubbingRequired to define behavior explicitlyOptional; real object methods are default
Dependency IsolationComplete isolationPartial isolation
Use CaseTesting interactions with dependenciesWhen you want to use part of a real object
Default Return ValuesDefault values (e.g., 0, null)Actual method returns value unless overridden
VerificationCan verify method interactionsCan verify method interactions

Additional Considerations

Use Cases

  • Mocking is ideal for testing new functionalities independently, ensuring the test remains unaffected by outside factors.
  • Spying is particularly useful when refactoring or expanding existing codebases, where the behavior of certain methods is complex or crucial to maintain during the tests.

Performance

  • Mocking usually incurs less overhead as it avoids real object creation and logic execution.
  • Spying might involve more computational resources since it involves real calls and potentially complex logic.

Limitations

  • Mocking is less effective when parts of the real implementation are necessary for the test's logic.
  • Spying can complicate testing when there are methods that need to be overridden extensively to avoid unwanted side effects.

Practical Tips

  • Favor mocking for pure unit tests where dependencies can be completely isolated.
  • Consider spying when the internal state management but not the behavior is your test focus, or where complex internal logic is beneficial.

In summary, both mocking and spying are valuable tools in the programmer’s toolkit when writing unit tests, but they serve different purposes and are best suited to different scenarios. Understanding their differences helps optimize test reliability and maintainability.


Course illustration
Course illustration

All Rights Reserved.