Mockito is a popular open-source mocking library for Java, and as such, it’s useful for those wanting to unit test their code. However, some of its features can confuse newcomers, particularly regarding spies. What’s a Mockito spy, and how does it differ from a mock? When should you use it? These are some of the questions we’ll be covering.
We’ll open the post by covering some fundamentals. You’ll learn
- what a Mockito spy is,
- how it’s different from a mock,
- when should you use it, and
- whether a spy is a test double—and what test doubles are, to begin with.
With these fundamentals covered, you’ll be ready to roll up your sleeves and get to work, learning how to use spies in practice.
Mockito Spy: Fundamentals
You’re probably itching to play with code, but be patient. Let’s first get some basics out of the way.
What Is a Mockito Spy?
A spy in Mockito is a special object used in unit testing. The spy object wraps a real object, allowing you to define fixed responses from certain methods and track its interactions with the SUT (system under test.)
Let’s see a super simple Mockito spy example:
RabbitMQMessagingPublisher spyPublisher = Mockito.spy(RabbitMQMessagingPublisher.class); var sut = new OrderService(spyPublisher); UUID orderId = UUID.fromString("bda91556-bd06-43a2-9a61-18bbab7f02a3"); sut.addOrder(orderId); String expectedMessage = "Order with id [bda91556-bd06-43a2-9a61-18bbab7f02a3] registered."; verify(spyPublisher, times(1)).publishMessage(expectedMessage);
The example above shows the creation of a spy object that spies on the RabbitMQMessagingPublisher class. The class is responsible for publishing messages to a queue or topic on RabbitMQ.
Next, we create an instance of the SUT (the OrderService class.) We invoke the addOrder method on the SUT, which is responsible for adding a new order to the database and publishing a message to a specified queue. The message likely includes details about the order and which other services (e.g., the payment service) can consume and use to perform their respective tasks. In this example, the addOrder method only receives a universally unique identifier (UUID), but this is a simplified version of a more realistic scenario.
Finally, we use Mockito’s verify method to check whether the method was called exactly once.
We’ll expand on this example later on. From now, keep reading to learn more about Mockito spy fundamentals.
What Is the Difference Between Spy and Mock?
You can consider a Mockito spy a special kind of mock. More specifically, it’s a partial mock:
- with a mock object, you stub out—that is, replace with a fake implementation—all methods from the real object
- with a spy, you can stub out some methods and not others, resorting to the real implementation
Additionally, as you’ve seen, a spy allows you to track the interactions with the mocked object, ensuring certain methods are or aren’t called as expected. A regular mock object—not a spy—can verify interactions between the SUT and the dependency. This is a peculiarity from Mockito, and we’ll discuss it later.
When Should a Spy Be Used In Mockito?
Use a Mockito spy in the following scenarios:
- You need to mock an object partially. That is, you want to stub out some methods but use the real implementation in others.
- You want to track the iterations that happened with the object.
In short, you’d choose spy over mock when you still need to rely on some real methods of the object.
Expand Your Test Coverage
Is Spy a Test Double?
The software testing lexicon provides a rich vocabulary for discussing objects that replace real implementation during unit testing. Until now, you’ve read about mocks and stubs, but there are more terms like doubles, dummies, fakes, and stubs.
First of all: is a spy a test double? Yes, it is. As defined by Gerard Meszaros in his book xUnit Test Patterns: Refactoring Test Code, test double is the generic term for any “fake” object we use in place of an actual implementation during tests. The other terms refer to specific types of test doubles.
Here’s Martin Fowler defining the other terms:
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
- Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
In practice, however, most people don’t use all of these terms—many probably aren’t even aware of them. The industry seems to have coalesced around mock as the generic term for test doubles. Interestingly enough, it seems that, according to the definition above, most people are thinking of stubs when they use the term mock.
Unfortunately, Mockito’s terminology throws more confusion into the mix, blurring the definition between mocks and spies. In Mockito, as you’ve seen, you can also verify interactions when using a mock object.
How to Use Mockito Spy in Practice
Now you know the what and when of spies in Mockito. It’s time to get your hands dirty with the “how.” Let’s begin by reviewing some prerequisites.
Requirements
There are some requirements if you want to follow along with the post:
- You need to have a working installation of the Java SDK.
- Experience with unit testing is helpful.
- Some experience with Java is assumed.
I’ll use IntelliJ IDEA as my IDE and Maven as my build automation tool. You can use other tools you’re more comfortable with, but it might make the post slightly harder to follow.
Creating a Sample Project
I’ll start by creating a simple project using IntelliJ IDEA:
Do the same using the tools you prefer. Then, create a class called OrderService and paste the following code on it:
import java.util.UUID; import java.util.HashMap; public class OrderService { private HashMap<UUID, Order> orders; public OrderService() { this.orders = new HashMap<UUID, Order>(); } public boolean addOrder(UUID orderId) { if (orders.containsKey(orderId)) { return false; } Order newOrder = new Order(orderId); orders.put(orderId, newOrder); return true; } }
Add the Order class as well:
import java.util.UUID; public class Order { private UUID orderId; public Order(UUID orderId) { this.orderId = orderId; } public UUID getOrderId() { return orderId; } }
Then, create the RabbitMQMessagingPublisher class:
public class RabbitMQMessagePublisher { public void publishMessage(String message) { } }
For our purposes in this tutorial, it doesn’t even need to have an implementation.
Now, change the constructor of OrderService, so it takes an instance of RabbitMQMessagingPublisher:
public OrderService(RabbitMQMessagingPublisher publisher)
Next, change the addOrder method, adding the following line just before the last return statement:
publisher.publishMessage(String.format("Order with id [%s] registered.", orderId));
Writing the Test
We’re finally ready to start writing tests. I’ll start by adding the JUnit5 dependency to my pom.xml:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.1</version> <scope>test</scope> </dependency>
Then, the dependency for Mockito itself:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
Finally, add a new test class called OrderServiceTest and paste the following content on it:
import org.example.OrderService; import org.example.RabbitMQMessagingPublisher; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.UUID; import static org.mockito.Mockito.*; public class OrderServiceTest { @Test public void addOrder_eventIsPublishedToBroker() { RabbitMQMessagingPublisher spyPublisher = Mockito.spy(RabbitMQMessagingPublisher.class); var sut = new OrderService(spyPublisher); UUID orderId = UUID.fromString("bda91556-bd06-43a2-9a61-18bbab7f02a3"); sut.addOrder(orderId); String expectedMessage = "Order with id [bda91556-bd06-43a2-9a61-18bbab7f02a3] registered."; verify(spyPublisher, times(1)).publishMessage(expectedMessage); } }
A few things are going on here, so let’s unpack it all. The method starts by creating a spy on the RabbitMQMessagingPublisher class. It then creates an instance of OrderService, with the spy object as an argument.
Then a new UUID is generated to represent the ID of an order, and it’s passed to the addOrder method on the OrderService object—called sut after system under test. Finally, we use the Mockito spy verify method to see that the publishMessage method was called exactly once, with the expected message we defined in the previous line.
Mockito Spy: Miscellaneous Tips
Before wrapping up, let’s share some final tips on Mockito that will certainly be of value to you.
Mockito Spy Constructor Requirement
If you want to spy on a class using Mockito, it must have at least one parameterless constructor. Suppose you have the following class:
public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } }
Then you try to spy on it:
var personSpy = Mockito.spy(Person.class);
That will fail in run time, and you’ll see an error message telling you the class needs a parameterless constructor. Add one to the class, and you’ll be good to go. But what if you can’t or won’t add that parameterless constructor for some reason?
In that case, you can instantiate the class, passing the arguments as needed, then spying on the instance itself:
var person = new Person("John Doe", 25); var personSpy = Mockito.spy(person);
Mockito Spy Static Method
Originally, it wasn’t possible to mock or spy static methods using Mockito, but this changed in more recent releases. While we have an entire post dedicated to mocking static methods with Mockito, let’s briefly show an example here. First, the SUT, the simplest class I could think of:
public class Calculator { public static int add(int a, int b) { return a + b; } }
How can you mock/spy this class? To start, ensure you import the org.mockito.MockedStatic package. Also, in your project’s dependencies, make sure to declare the mockito-inline dependency instead of mockito-core.
Now, for the test code:
try (MockedStatic<Calculator> mockedStatic = Mockito.mockStatic(Calculator.class)) { System.out.println("Result of 1+2: " + Calculator.add(1,2)); mockedStatic.verify(() -> Calculator.add(1,2)); }
The syntax looks different, but the idea is the same: the line that prints the result of the static method is a proxy for calling the system under test. Then, we verify the method was called with the specified set of parameters.
If you want to learn more about mocking static methods, refer to the post we shared.
Mockito Spy: Learn It, Leverage It, Leave It (If It Makes Sense)
Unit testing is essential for software quality. It’s also hard, especially when you factor in testing code with hard-to-test dependencies. That’s where the concept of mocking and spying comes in handy.
In this post, you’ve learned about Mockito, an open-source mocking solution for Java. You’ve learned what Mockito is, how mocking differs from spying, and when you should use a Mockito spy.
Before parting ways, a word of caution: testing that relies too much on mocking can result in fragile tests, due to relying too much on implementation details. To learn more about that, google “London vs. Chigago unit testing.”
Thanks for reading!