Mastering Mockito: Returning Fresh Stream For Multiple Calls To Mocked Method
When we mock a method that returns a Stream
we need to make sure we return a fresh Stream
on each invocation to support multiple calls to the mocked method. If we don’t do that, the stream will be closed after the first call and subsequent calls will throw exceptions. We can chain multiple thenReturn
calls to return a fresh Stream
each time the mocked method is invoked. Or we can use multiple arguments with the thenReturn
method, where each argument is returned based on the number of times the mocked method is invoked. So on the first invocation the first argument is returned, on second invocation the second argument and so on. This works when we know the exact number of invocations in advance. But if we want to be more flexible and want to support any number of invocations, then we can use thenAnswer
method. This method needs an Answer
implementation that returns a value on each invocation. The Answer
interface is a functional interface with only one method that needs to be implemented. We can rely on a function call to implement the Answer
interface where the function gets a InvocationOnMock
object as parameter and returns a value. As the function is called each time the mocked method is invoked, we can return a Stream
that will be new each time.
In the following example we use chained method calls using thenReturn
and we use thenAnwer
to support multiple calls to our mocked method temperature
that returns a Stream
of Double
values:
package mrhaki;
import org.junit.jupiter.api.Test;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MockReturnMultipleStreams {
// Simple interface to return a stream of
// temperature values for a given city.
interface Weather {
Stream<Double> temperature(String city);
}
// Simple class that uses Weather interface.
static class SubjectUnderTest {
private final Weather weather;
SubjectUnderTest(Weather weather) {this.weather = weather;}
public String weatherReport(String city) {
// By invoking the methods celcius and fahrenheit we will
// invoke the weather.temperature method twice.
return String.format("The temperature in %s is %.1f degrees Celcius or %.1f degrees Fahrenheit.",
city, celcius(city), fahrenheit(city));
}
private double celcius(String city) {
return weather.temperature(city).findAny().get();
}
private double fahrenheit(String city) {
return (celcius(city) * 9/5) + 32;
}
}
private final Weather weather = mock(Weather.class);
private final SubjectUnderTest subjectUnderTest = new SubjectUnderTest(weather);
@Test
void shouldReturnCorrectWeatherReport() {
// given
// Return type of the mocked method temperature is a Stream.
// On the first call in the subjectUnderTest instance the stream
// is closed, so the second call will give an exception that
// the stream is already been operated upon or closed.
// To support the second call we need to return a new stream
// with the same content.
// If we need to support more calls than two we need
// to add more thenReturn statements.
// See the next test method for an example with thenAnswer
// that supports multiple calls more easily.
double temperature = 21.0;
when(weather.temperature("Tilburg"))
// First call
.thenReturn(Stream.of(temperature))
// Second call
.thenReturn(Stream.of(temperature));
// Alternative syntax:
// when(weather.temperature("Tilburg"))
// .thenReturn(Stream.of(temperature), Stream.of(temperature));
// when
String result = subjectUnderTest.weatherReport("Tilburg");
// then
assertThat(result).isEqualTo("The temperature in Tilburg is 21,0 degrees Celcius or 69,8 degrees Fahrenheit.");
}
@Test
void shouldReturnCorrectWeatherReport2() {
// given
// Return type of the mocked method temperature is a Stream.
// On the first call in the subjectUnderTest instance the stream
// is closed, so the second call will give an exception that
// the stream is already been operated upon or closed.
// To support the second call we can use thenAnswer method
// which will return a fresh Stream on each call.
// Now the number of calls is not limited, because on each
// invocation a fresh Stream is created.
when(weather.temperature("Tilburg"))
.thenAnswer(invocationOnMock -> Stream.of(21.0));
// when
String result = subjectUnderTest.weatherReport("Tilburg");
// then
assertThat(result).isEqualTo("The temperature in Tilburg is 21,0 degrees Celcius or 69,8 degrees Fahrenheit.");
}
}
Written with Mockito 3.12.4.