Mock multiple calls with Mockito

May 03, 2022 No comments Mockito Java JUnit

1. Introduction

In this article, we will show how to use Mockito to configure multiple method calls in such a way that they will return a different value on each call. We will present several ways to achieve that using the Mockito method calls chain and other thenAnswer, doAnswer methods with specific InvocationOnMock implementation.

2. Testing class

In this example we will use a simple BasketService class as our base test class:

package com.frontbackend.libraries.mockito.service;

import com.frontbackend.libraries.mockito.model.Basket;
import com.frontbackend.libraries.mockito.model.BasketEntry;
import com.frontbackend.libraries.mockito.model.Product;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class BasketService {

    private final Basket basket;

    public void addProductToBasket(Product product, double quantity) {
        BasketEntry basketEntry = new BasketEntry(product, quantity);
        basket.getEntries()
              .add(basketEntry);
    }

    public double getTotalAmount() {
        return basket.getEntries()
                     .stream()
                     .mapToDouble(this::getBasketEntryPrice)
                     .sum();
    }

    private double getBasketEntryPrice(BasketEntry basketEntry) {
        return basketEntry.getProduct()
                          .getPrice()
                * basketEntry.getQuantity();
    }
}

The Basket will be aggregating all BasketEntries:

package com.frontbackend.libraries.mockito.model;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class Basket {

    private final List<BasketEntry> entries = new ArrayList<>();
}

The BasketEntry will contain Product with quantity:

package com.frontbackend.libraries.mockito.model;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class BasketEntry {

    private final Product product;
    private final double quantity;
}

Finally, the Product will be our item that we will put in the basket:

package com.frontbackend.libraries.mockito.model;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class Product {

    private final String name;
    private final double price;

}

3. Using when(mock.method()).thenReturn(...).thenReturn(...) approach

Mockito allows us to chain the thenReturn(...) to set a different method behavior each time it is called.

In the following JUnit test we used thenReturn() chain to change banana.getPrice() method return value each time this method is called:

package com.frontbackend.libraries.mockito;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.frontbackend.libraries.mockito.model.Basket;
import com.frontbackend.libraries.mockito.model.Product;
import com.frontbackend.libraries.mockito.service.BasketService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoMultipleCallsTest {

    @Spy
    private Basket basket;

    @Mock
    private Product banana;

    @InjectMocks
    private BasketService basketService;

    @Test
    public void shouldCountTotalPriceCorrectly_approach1() {
        // Given
        when(banana.getPrice()).thenReturn(2.00)
                .thenReturn(3.00)
                .thenReturn(4.00);

        // When
        basketService.addProductToBasket(banana, 1); // price = 1 * 2.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 3.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 4.00

        double totalAmount = basketService.getTotalAmount();

        // Then
        assertEquals("Total price should be 9", 9.0, totalAmount, 0);
        verify(basket, times(4)).getEntries(); // adding 3 products + counting total price
    }
}

In this example, the following chain was used:

when(banana.getPrice()).thenReturn(2.00).thenReturn(3.00).thenReturn(4.00)

When a method banana.getPrice() is called for the first time, the value 2.00 will be returned. The next time the method is called the value 3.00 will be returned. Third time 4.00 is returned.

Each additional invocation on the mock will return the last thenReturn value - this will be 4.00 in our case.

Note that this will work with a mock, but, not with a spy. You could find some more info about this in an article about Why trying to spy on method is calling the original method in Mockito.

In short, if you need to prevent calling the original method you need to use doAnswer(...).when(someSpyObject).someMethod(...) or oReturn(...).doReturn(...).when(someSpyObject).method() - both approaches are explained in this article.

4. Using doReturn(...).doReturn(...).when(mock).method() approach

In this case, we used the ability to chain Mockito doReturn(...) methods to achieve the same effect:

package com.frontbackend.libraries.mockito;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.frontbackend.libraries.mockito.model.Basket;
import com.frontbackend.libraries.mockito.model.Product;
import com.frontbackend.libraries.mockito.service.BasketService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoMultipleCallsTest {

    @Spy
    private Basket basket;

    @Mock
    private Product banana;

    @InjectMocks
    private BasketService basketService;

    @Test
    public void shouldCountTotalPriceCorrectly_approach4() {
        // Given
        doReturn(2.00).doReturn(3.00)
                .doReturn(4.00)
                .when(banana)
                .getPrice();

        // When
        basketService.addProductToBasket(banana, 1); // price = 1 * 2.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 3.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 4.00

        double totalAmount = basketService.getTotalAmount();

        // Then
        assertEquals("Total price should be 9", 9.0, totalAmount, 0);
        verify(basket, times(4)).getEntries(); // adding 3 products + counting total price
    }
}

This approach will work with a mock and spy objects.

5. Using thenAnswer() method

In this example we created an anonymous Answer on an object with a private count variable to return a different value each time method getPrice() was called on the banana object:

package com.frontbackend.libraries.mockito;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.frontbackend.libraries.mockito.model.Basket;
import com.frontbackend.libraries.mockito.model.Product;
import com.frontbackend.libraries.mockito.service.BasketService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

@RunWith(MockitoJUnitRunner.class)
public class MockitoMultipleCallsTest {

    @Spy
    private Basket basket;

    @Mock
    private Product banana;

    @InjectMocks
    private BasketService basketService;

    @Test
    public void shouldCountTotalPriceCorrectly_approach2() {
        // Given
        when(banana.getPrice()).thenAnswer(new Answer<Double>() {
            private int count = 0;

            public Double answer(InvocationOnMock invocation) {
                count++;
                switch (count) {
                    case 1:
                        return 2.00;
                    case 2:
                        return 3.00;
                    case 3:
                        return 4.00;
                    default:
                        return 0.00;
                }
            }
        });

        // When
        basketService.addProductToBasket(banana, 1); // price = 1 * 2.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 3.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 4.00

        double totalAmount = basketService.getTotalAmount();

        // Then
        assertEquals("Total price should be 9", 9.0, totalAmount, 0);
        verify(basket, times(4)).getEntries(); // adding 3 products + counting total price
    }
}

6. Using doAnswer() method

In this approach we use an anonymous Answer class to handle each method call:

package com.frontbackend.libraries.mockito;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.frontbackend.libraries.mockito.model.Basket;
import com.frontbackend.libraries.mockito.model.Product;
import com.frontbackend.libraries.mockito.service.BasketService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

@RunWith(MockitoJUnitRunner.class)
public class MockitoMultipleCallsTest {

    @Spy
    private Basket basket;

    @Mock
    private Product banana;

    @InjectMocks
    private BasketService basketService;

    @Test
    public void shouldCountTotalPriceCorrectly_approach3() {
        // Given
        doAnswer(new Answer<Double>() {
            private int count = 0;

            public Double answer(InvocationOnMock invocation) {
                count++;
                switch (count) {
                    case 1:
                        return 2.00;
                    case 2:
                        return 3.00;
                    case 3:
                        return 4.00;
                    default:
                        return 0.00;
                }
            }
        }).when(banana)
                .getPrice();

        // When
        basketService.addProductToBasket(banana, 1); // price = 1 * 2.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 3.00
        basketService.addProductToBasket(banana, 1); // price = 1 * 4.00

        double totalAmount = basketService.getTotalAmount();

        // Then
        assertEquals("Total price should be 9", 9.0, totalAmount, 0);
        verify(basket, times(4)).getEntries(); // adding 3 products + counting total price
    }
}

The doAnswer() method should be used for spy objects.

7. Conclusion

In this article, we've outlined several ways to configure multiple method calls using Mockito.

As usual, code introduced in this article is available in our GitHub repository.

{{ message }}

{{ 'Comments are closed.' | trans }}