Creating Mocks and Spies in Mockito with Code Examples

Mockito Spy and Mocks Tutorial:

In this Mockito Tutorial series, our previous tutorial gave us an Introduction to Mockito Framework. In this tutorial, we will learn the concept of Mocks and Spies in Mockito.

What are Mocks and Spies?

Both Mocks and Spies are the types of test doubles, which are helpful in writing unit tests.

Mocks are a full replacement for dependency and can be programmed to return the specified output whenever a method on the mock is called. Mockito provides a default implementation for all the methods of a mock.

The Concept of Mocks and Spies in Mockito

What are Spies?

Spies are essentially a wrapper on a real instance of the mocked dependency. What this means is that it requires a new instance of the Object or dependency and then adds a wrapper of the mocked object over it. By default, Spies call real methods of the Object unless stubbed.

Spies do provide certain additional powers like what arguments were supplied to the method call, was the real method called at all etc.

In a nutshell, for Spies:

  • The real instance of the object is required.
  • Spies gives flexibility to stub some (or all) methods of the spied object. At that time, the spy is essentially called or referred to a partially mocked or stubbed object.
  • The interactions called on a spied object can be tracked for verification.

In generality, Spies are not very frequently used but can be helpful for unit testing legacy applications where the dependencies can’t be fully mocked.

For all the Mock and Spy description, we are referring to a fictitious class/object called ‘DiscountCalculator’ which we want to mock/spy.

It has some methods as shown below:

calculateDiscount – Calculates the discounted price of a given product.
getDiscountLimit – Fetches the upper limit discount limit for the product.

Creating Mocks

#1) Mock creation with Code

Mockito gives several overloaded versions of Mockito. Mocks method and allows creating mocks for dependencies.

Syntax:

Mockito.mock(Class<T> classToMock)

Example:

Suppose class name is DiscountCalculator, to create a mock in code:

DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)

It is important to note that Mock can be created for both interface or a concrete class.

When an object is mocked, unless stubbed all the methods return null by default.

DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);

#2) Mock creation with Annotations

Instead of mocking using static ‘mock’ method of Mockito library, it also provides a shorthand way of creating mocks using ‘@Mock’ annotation.

The biggest advantage of this approach is that it is simple and allows to combine declaration and essentially initialization. It also makes the tests more readable and avoids repeated initialization of mocks when the same mock is being used at several places.

In order to ensure Mock initialization through this approach, its required that we should call ‘MockitoAnnotations.initMocks(this)’ for the class under test. This is the ideal candidate to be part of ‘beforeEach’ method of Junit which ensures that mocks are initialized each time when a test is executed from that class.

Syntax:

@Mock private transient DiscountCalculator mockedDiscountCalculator;

Creating Spies

Similar to Mocks, Spies can also be created in 2 ways:

#1) Spy creation with Code

Mockito.spy is the static method that is used to create a ‘spy’ object/wrapper around the real object instance.

Syntax:

private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);

#2) Spy creation with Annotations

Similar to Mock, Spies can be created using @Spy annotation.

For Spy initialization as well you must ensure that MockitoAnnotations.initMocks(this) are called before the Spy is used in the actual test in order to get the spy initialized.

Syntax:

@Spy private transient ItemService spiedItemService = new ItemServiceImpl();

How to Inject Mocked Dependencies for the Class/Object under Test?

When we want to create a mock object of the class under test with the other mocked dependencies, we can use @InjectMocks annotation.

What this essentially does is that all the objects marked with @Mock (or @Spy) annotations are injected as Contractor or property injection into the class Object and then interactions can be verified on the final Mocked object.

Again, needless to mention, @InjectMocks is a shorthand against creating a new Object of the class and provides mocked objects of the dependencies.

Let us understand this with an Example:

Suppose, there is a class PriceCalculator, which has DiscountCalculator and UserService as dependencies which are injected via Constructor or Property fields.

So, in order to create the Mocked implementation for Price calculator class, we can use 2 approaches:

#1) Create a new instance of PriceCalculator and inject Mocked dependencies

@Mock
private transient DiscountCalculator mockedDiscountCalculator;

@Mock
private transient UserService userService;

@Mock
private transient ItemService mockedItemService;

private transient PriceCalculator priceCalculator;

@BeforeEach
public void beforeEach()
{
MockitoAnnotations.initMocks(this);
priceCalculator = new PriceCalculator(mockedDiscountCalculator, userService, mockedItemService);
}

#2) Create a mocked instance of PriceCalculator and inject dependencies through @InjectMocks annotation

@Mock
private transient DiscountCalculator mockedDiscountCalculator;

@Mock
private transient UserService userService;

@Mock
private transient ItemService mockedItemService;

@InjectMocks
private transient PriceCalculator priceCalculator;

@BeforeEach
public void beforeEach()
{
  MockitoAnnotations.initMocks(this);

InjectMocks annotation actually tries to inject mocked dependencies using one of the below approaches:

  1. Constructor Based Injection – Utilizes Constructor for the class under test.
  2. Setter Methods Based – When a Constructor is not there, Mockito tries to inject using property setters.
  3. Field Based – When the above 2 are not available then it directly tries to inject via fields.

Tips & Tricks

#1) Setting up different stubs for different calls of the same method:


When a stubbed method is called multiple times inside the method under test (or the stubbed method is in the loop and you want to return different output each time), then you can set up Mock to return different stubbed response each time.

For Example: Suppose you want ItemService to return a different item for 3 consecutive calls and you have Items declared in your method under tests as Item1, Item2, and Item3, then you can simply return these for 3 consecutive invocations using the below code:

@Test
public void calculatePrice_withCorrectInput_returnsValidResult()
{
// Arrange
ItemSku item1 = new ItemSku();
ItemSku item2 = new ItemSku();
ItemSku item3 = new ItemSku();

// Setup Mocks
when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1, item2, item3);

// Assert
//TODO - add assert statements
}

#2) Throwing Exception through Mock: This is a very common scenario when you want to test/verify a downstream/dependency throwing an exception and check the behavior of the system under test. However, in order to throw an exception by Mock, you will need to setup stub using thenThrow.

@Test
public void calculatePrice_withInCorrectInput_throwsException()
{
// Arrange
ItemSku item1 = new ItemSku();

// Setup Mocks
when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString()));

// Assert
//TODO - add assert statements
}

For matches like anyInt() and anyString(), don’t get intimidated as they will be covered in the upcoming articles. But in essence, they just give you the flexibility to provide any Integer and String value respectively without any specific function arguments.

Code Examples – Spies & Mocks

As discussed earlier, both Spies and Mocks are the type of test doubles and have their own usages.

While spies are useful for testing legacy applications (and where mocks are not possible), for all the other nicely written testable methods/classes, Mocks suffices most of the Unit testing needs.

For the same Example: Let us write a test using Mocks for PriceCalculator -> calculatePrice method (The method calculates itemPrice less of the applicable discounts)

The PriceCalculator class and the method under test calculatePrice looks as shown below:

public class PriceCalculator {

public DiscountCalculator discountCalculator;
   public UserService userService;
   public ItemService itemService;

public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService)
{ this.discountCalculator = discountCalculator;
        this.userService = userService;
        this.itemService = itemService;
}

public double calculatePrice(int itemSkuCode, int customerAccountId)
  {
double price = 0;

// get Item details
ItemSku sku = itemService.getItemDetails(itemSkuCode);

// get User and calculate price
CustomerProfile customerProfile = userService.getUser(customerAccountId);

      double basePrice = sku.getPrice();
price = basePrice - (basePrice* (sku.getApplicableDiscount() + 
customerProfile.getExtraLoyaltyDiscountPercentage())/100);

        return price;
}
}

Now let’s write a positive test for this method.

We are going to stub userService and item service as mentioned below:

  1. UserService will always return CustomerProfile with the loyaltyDiscountPercentage set to 2.
  2. ItemService will always return an Item with the basePrice of 100 and applicableDiscount of 5.
  3. With the above values, the expectedPrice returned by the method under test comes out to be 93$.

Here is the code for test:

@Test
public void calculatePrice_withCorrectInput_returnsExpectedPrice()
{
// Arrange
ItemSku item1 = new ItemSku();
item1.setApplicableDiscount(5.00);
item1.setPrice(100.00);

CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

      double expectedPrice = 93.00;

// Setting up stubbed responses using mocks
when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1);
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);

// Act
double actualPrice = priceCalculator.calculatePrice(123,5432);

// Assert
assertEquals(expectedPrice, actualPrice);
}

As you can see, in the above test – We are asserting that the actualPrice returned by the method equals the expectedPrice i.e. 93.00.

Now, let’s write a test using Spy.

We will Spy the ItemService and will code the ItemService implementation in a way that it always returns an item with the basePrice 200 and applicableDiscount of 10.00% (rest of the mock setup remains same) whenever its called with skuCode of 2367.

@InjectMocks
private PriceCalculator priceCalculator;

@Mock
private DiscountCalculator mockedDiscountCalculator;

@Mock
private UserService mockedUserService;

@Spy
private ItemService mockedItemService = new ItemServiceImpl();

@BeforeEach
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}

@Test
public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice()
{
// Arrange
CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

     double expectedPrice = 176.00;

// Setting up stubbed responses using mocks
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);

// Act
double actualPrice = priceCalculator.calculatePrice(2367,5432);

// Assert
assertEquals(expectedPrice, actualPrice);

Now, let's see an Example of an exception being thrown by ItemService as the available Item quantity was 0. We will set up mock to throw an exception.

@InjectMocks
private PriceCalculator priceCalculator;

@Mock
private DiscountCalculator mockedDiscountCalculator;

@Mock
private UserService mockedUserService;

@Mock
private ItemService mockedItemService = new ItemServiceImpl();

@BeforeEach
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}

@Test
public void calculatePrice_whenItemNotAvailable_throwsException()
{
// Arrange
CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

     double expectedPrice = 176.00;

// Setting up stubbed responses using mocks
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);
when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString()));

// Act & Assert
assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234));
}

With the above examples, I’ve tried to explain the concept of Mocks & Spies and how they can be combined to create effective and useful Unit tests.

There can be multiple combinations of these techniques to get a suite of tests which enhance coverage of the method under test, thereby ensuring a great level of confidence in the code and makes the code more resistant to regression bugs.

Source Code

Interfaces

DiscountCalculator

public interface DiscountCalculator {
double calculateDiscount(ItemSku itemSku, double markedPrice);
     void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile);
}

ItemService

public interface ItemService {
  ItemSku getItemDetails(int skuCode) throws ItemServiceException;
}

UserService

public interface UserService {

void addUser(CustomerProfile customerProfile);
  void deleteUser(CustomerProfile customerProfile);
CustomerProfile getUser(int customerAccountId);
}

Interface Implementations

DiscountCalculatorImpl

public class DiscountCalculatorImpl implements DiscountCalculator {
@Override
public double calculateDiscount(ItemSku itemSku, double markedPrice) {
return 0;
}

@Override
public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) {

  }
}

ItemServiceImpl

public class DiscountCalculatorImpl implements DiscountCalculator {
@Override
public double calculateDiscount(ItemSku itemSku, double markedPrice) {
return 0;
}

@Override
public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) {

  }
}

Models

CustomerProfile

public class CustomerProfile {

private String customerName;
  private String loyaltyTier;
  private String customerAddress;
  private String accountId;
  private double extraLoyaltyDiscountPercentage;

  public double getExtraLoyaltyDiscountPercentage() {
return extraLoyaltyDiscountPercentage;
}

public void setExtraLoyaltyDiscountPercentage(double extraLoyaltyDiscountPercentage) {
this.extraLoyaltyDiscountPercentage = extraLoyaltyDiscountPercentage;
}

public String getAccountId() {
return accountId;
}

public void setAccountId(String accountId) {
this.accountId = accountId;
}

public String getCustomerName() {
return customerName;
}

public void setCustomerName(String customerName) {
this.customerName = customerName;
}

public String getLoyaltyTier() {
return loyaltyTier;
}

public void setLoyaltyTier(String loyaltyTier) {
this.loyaltyTier = loyaltyTier;
}

public String getCustomerAddress() {
return customerAddress;
}

public void setCustomerAddress(String customerAddress) {
this.customerAddress = customerAddress;
}
}

ItemSku

public class ItemSku {
private int skuCode;
   private double price;
   private double maxDiscount;
   private double margin;
   private int totalQuantity;
   private double applicableDiscount;

   public double getApplicableDiscount() {
return applicableDiscount;
}

public void setApplicableDiscount(double applicableDiscount) {
this.applicableDiscount = applicableDiscount;
}

public int getTotalQuantity() {
return totalQuantity;
}

public void setTotalQuantity(int totalQuantity) {
this.totalQuantity = totalQuantity;
}

public int getSkuCode() {
return skuCode;
}

public void setSkuCode(int skuCode) {
this.skuCode = skuCode;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public double getMaxDiscount() {
return maxDiscount;
}

public void setMaxDiscount(double maxDiscount) {
this.maxDiscount = maxDiscount;
}

public double getMargin() {
return margin;
}

public void setMargin(double margin) {
this.margin = margin;
}
}

Class Under Test – PriceCalculator

public class PriceCalculator {

public DiscountCalculator discountCalculator;
  public UserService userService;
  public ItemService itemService;
  public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService){ 
this.discountCalculator = discountCalculator;
  this.userService = userService;
  this.itemService = itemService;
}

public double calculatePrice(int itemSkuCode, int customerAccountId)
 {
double price = 0;

// get Item details
ItemSku sku = itemService.getItemDetails(itemSkuCode);

// get User and calculate price
CustomerProfile customerProfile = userService.getUser(customerAccountId);

     double basePrice = sku.getPrice();
price = basePrice - (basePrice* (sku.getApplicableDiscount() +
customerProfile.getExtraLoyaltyDiscountPercentage())/100);

     return price;
}
}

Unit Tests – PriceCalculatorUnitTests

public class PriceCalculatorUnitTests {

@InjectMocks
private PriceCalculator priceCalculator;

@Mock
private DiscountCalculator mockedDiscountCalculator;

@Mock
private UserService mockedUserService;

@Mock
private ItemService mockedItemService;

@BeforeEach
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}

@Test
public void calculatePrice_withCorrectInput_returnsExpectedPrice()
{
// Arrange
ItemSku item1 = new ItemSku();
item1.setApplicableDiscount(5.00);
item1.setPrice(100.00);

CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

       double expectedPrice = 93.00;

// Setting up stubbed responses using mocks
when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1);
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);

// Act
double actualPrice = priceCalculator.calculatePrice(123,5432);

// Assert
assertEquals(expectedPrice, actualPrice);
}

@Test
  @Disabled
// to enable this change the ItemService MOCK to SPY
public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice()
  {
// Arrange
CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

      double expectedPrice = 176.00;

// Setting up stubbed responses using mocks
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);

// Act
double actualPrice = priceCalculator.calculatePrice(2367,5432);

// Assert
assertEquals(expectedPrice, actualPrice);
}

@Test
public void calculatePrice_whenItemNotAvailable_throwsException()
{
// Arrange
CustomerProfile customerProfile = new CustomerProfile();
customerProfile.setExtraLoyaltyDiscountPercentage(2.00);

      double expectedPrice = 176.00;

// Setting up stubbed responses using mocks
when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile);
when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString()));

// Act & Assert
assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234));
}

}

Different Types of Matchers provided by Mockito are explained in our upcoming tutorial.

PREV Tutorial | NEXT Tutorial