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:
Copy
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
:
Copy
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
:
Copy
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:
Copy
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:
Copy
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:
Copy
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:
Copy
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:
Copy
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:
Copy
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 .
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}