So you're using Mockito for automated unit tests and now you're at the point where you need to know the difference between a mock and a spy?

Okay. I'll help you with that.

Let's start by keeping it simple:

  • Mock - mocks everything
  • Spy - mocks some things

There you go.

Now let's dive into it a little deeper.

Mockingbird

First up, the mock.

Use a mock when you want to mock the entire object's behavior.

The upside: you never have to worry about accidentally performing an integration test when you just want to perform a mock test. And you'll also likely get some speed benefits out of the deal as well.

The downside? Well, you have to stub every method that you're going to test.

That can get fugly.

The Spy Who Loved Me

If you want the advantages of a mock without the requirement to stub every method, go with a spy.

You get the best of both worlds: you can stub the methods that require some level of integration while keeping the methods that perform basic business logic.

That advantage is why lots of developers prefer spies over mocks.

Let's take a look at a spy in action.

The Use Case

Let's say we've got an ecommerce application. Users who login to the application are customers.

Some customers want to view the value of their most recent orders. To do that, the system will go to the database, get the most recent orders, and return a sum of the value of those orders.

When it comes to testing, the database retrieval part needs to get stubbed. But the summation part doesn't. That's just simple math.

Now, let's look at some code.

At Your Service

Here's a simple class called CustomerService:

public class CustomerService {

	private CustomerDao customerDao = new CustomerDao();
	
	public List<Order> getRecentCustomerOrders(int numberOfOrders) {
		return customerDao.getRecentCustomerOrders(numberOfOrders);
	}
	
	public Integer getLastOrderValue() {
		Integer value = 0;
		
		List<Order> orders = getRecentCustomerOrders(1);
		if (orders != null && orders.size() > 0) {
			value = orders.get(0).getValue();
		}
		
		return value;
	}
}

It's a fairly standard service-level class. It uses a data access object (DAO) to handle database-related activities.

You can see that the CustomerDao class gets instantiated in the first line. If you're using Spring, you'd probably opt for an @Autowired annotation there. But we're keeping things simple for the sake of this guide.

The first method uses the DAO to get the customer's most recent orders. The integer argument determines how many orders it will retrieve.

The orders get retrieved in reverse chronological order. So the most recent order is first.

That method, by the way, is the one you'll want to stub. Because you don't want any database integration happening in your unit test.

The second method, getLastOrderValue(), requires no stubbing. It just gets the most recent order and returns its value. 

It does that by getting the most recent 1 order from that first method. That's going to return a List of a single Order object. From there, it's a simple matter of getting the value of that single order and returning it.

Note that order values are returned as cents. So a value of 100 is 100 cents or 1 dollar.

Again: we're keeping things simple here.

And if you're curious, here's what the DAO class looks like:

public class CustomerDao {
	
	public List<Order> getRecentCustomerOrders(int numberOfOrders) {
		//does nothing for now
		return null;
	}
}

Yes, it's okay that it does nothing here. That's because we're mocking it anyway.

In your real-world application, that's where you'd put the code that retrieves the latest orders from the database.

Dependencies, We Have a Few

Before we get to the testing part, take a look at the dependencies you'll need in the POM file:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>us.careydevelopment.mockito</groupId>
	<artifactId>CrudApp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<dependencies>
		<dependency>
		    <groupId>org.mockito</groupId>
		    <artifactId>mockito-junit-jupiter</artifactId>
		    <version>4.2.0</version>
		    <scope>test</scope>
		</dependency>
		<dependency>
		    <groupId>org.junit.jupiter</groupId>
		    <artifactId>junit-jupiter-api</artifactId>
		    <version>5.8.0</version>
		    <scope>test</scope>
		</dependency>
	</dependencies>
</project>

You should use those same dependencies too if you're following along here.

If you're using Spring, you might already have what you need from the parent project. Your IDE will give you a little warning if you try to add a dependency you've already added.

Trains, Planes, and Models

Here's what the model objects look like:

public class Customer {

	public Integer id;
	public String firstName;
	public String lastName;
	public String phoneNumber;

    //getters and setters
}

That represents the customer (or user in this case). 

Here's the Order class:

public class Order {

	private Integer id;
	private String description;
	private Integer value;
	private Customer customer;

    //getters and setters
}

Nothing too special there. Each order has its own ID, description, and value. It's also associated with a specific customer.

Harness Your Inner Power

Now in one of your test packages, create some harnesses that will give you test data. 

Here's one for customer:

public class CustomerHarness {

	public static final Integer ID = 37;
	public static final String LAST_NAME = "Reesee";
	public static final String FIRST_NAME = "Beatrice";
	public static final String PHONE_NUMBER = "919-555-1212";
	
	public static Customer createCustomer() {
		Customer customer = new Customer();
		
		customer.setFirstName(FIRST_NAME);
		customer.setLastName(LAST_NAME);
		customer.setPhoneNumber(PHONE_NUMBER);
		customer.setId(ID);
		
		return customer;
	}
}

And here's one for orders:

public class OrderHarness {

	public static final Integer ORDER_1_ID = 103;
	public static final Integer ORDER_2_ID = 104;
	
	public static final String ORDER_1_DESC = "Booties";
	public static final String ORDER_2_DESC = "Nail Polish";
	
	public static final Integer ORDER_1_VAL = 750;
	public static final Integer ORDER_2_VAL = 1150;

	public static List<Order> createOrders() {
		Customer customer = CustomerHarness.createCustomer();
		
		Order order1 = new Order();
		order1.setCustomer(customer);
		order1.setDescription(ORDER_1_DESC);
		order1.setId(ORDER_1_ID);
		order1.setValue(ORDER_1_VAL);
		
		Order order2 = new Order();
		order2.setCustomer(customer);
		order2.setDescription(ORDER_2_DESC);
		order2.setId(ORDER_2_ID);
		order2.setValue(ORDER_2_VAL);
		
		List<Order> orders = List.of(order1, order2);
		
		return orders;
	}
	
	public static List<Order> createMostRecentOrder() {
		List<Order> orders = createOrders();
		
		List<Order> recentOrder = List.of(orders.get(0));
		
		return recentOrder;
	}
}

The first method, createOrders(), gives you all the customer's orders. In this case, there are just two of them.

The second method, createMostRecentOrder(), gives you the customer's most recent order as a List of one object.

Now that you've got your foundation in place, it's time to do some actual testing.

Testing

Here's the unit test code:

@ExtendWith(MockitoExtension.class)
public class CustomerServiceTest {

	@Spy
	CustomerService service;
	
	@Test
	public void testGetLastOrderValue() {
		Mockito.when(service.getRecentCustomerOrders(1)).thenReturn(OrderHarness.createMostRecentOrder());
		
		Integer value = service.getLastOrderValue();
		
		Assertions.assertEquals(OrderHarness.ORDER_1_VAL, value);
	}
}

Note that the CustomerService object is annotated with @Spy, not @Mock.

Unsurprisingly, that makes it a spy instead of a mock.

Also unsurprisingly, that means you have to stub out any methods you don't want executed as written.

The class itself only has a single method: testGetLastOrderValue(). That's going to test the getLastOrderValue() method on the CustomerService object.

The method starts off with a stub. Remember: you need to stub out any methods that integrate with the database. 

The getRecentCustomerOrders() method interacts with the databse. So that one needs a stub.

It's stubbed with Mockito.when(). You can do the static import if you want to get rid of the Mockito part, but then you might run into some trouble with auto-imports.

Anyhoo, the thenReturn() clause of the stub tells the method to return the List of a single order as determined by the createMostRecentOrder() method in OrderHarness.

You saw that code in the previous section.

The next line just invokes getLastOrderValue() from the CustomerService object. When that method in turn calls getRecentCustomerOrders(1), the stub will get invoked. There won't be any call to the database.

And so the method will return the value of the most recent order as dictated by the harness.

The last line in the method handles the assertion.

Now run that code and it should give you a passing grade.

Wrapping It Up

There you go. Now you know the difference between a mock and a spy.

Even better: you know how to use a spy.

Take what you've learned here and start putting it into your own unit tests. I think you'll find you'll use spies quite frequently.

Have fun!

Photo by cottonbro from Pexels