Reputation: 1036
One of the interactions I want to test is that a class Foo
is supposed to pass a Stream<Changes>
to FooListener.someChangesHappened
. Is there a Mockito idiom to verify that a stream contained the expected objects?
Upvotes: 6
Views: 12604
Reputation: 1900
Just encountered a similar problem where I needed to mock and verify a method which takes a Stream
and returns a Stream
. (It basically performs some filtering - which was tested somewhere else, so mocking it to simply forward the input made sense.)
My solution was to save the Stream
into a List
then perform assertions on that.
This approach isn't limited to Stream
either, capturing inputs like this can make assertions easier, as you aren't limited to verify
.
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class MockitoStreamVerifyTest {
@Mock private FooListener mockFooListener;
@InjectMocks private Foo foo;
@Test
void testContains() {
// Given
List<Changes> changes = mockAndCaptureFooListener();
// When
foo.run();
// Then
assertThat(changes).containsExactly(Changes.ONE, Changes.TWO, Changes.THREE);
}
private List<Changes> mockAndCaptureFooListener() {
List<Changes> changes = new ArrayList<>();
when(mockFooListener.someChangesHappened(any()))
.thenAnswer(
invocation -> {
Stream<Changes> stream = invocation.getArgument(0);
List<Changes> savedStream = stream.toList();
// capture the input
changes.addAll(savedStream);
// return the input
return savedStream.stream();
});
return changes;
}
interface FooListener {
Stream<Changes> someChangesHappened(Stream<Changes> stream);
}
enum Changes {
ONE,
TWO,
THREE,
FOUR
}
static class Foo {
private final FooListener fooListener;
Foo(FooListener fooListener) {
this.fooListener = fooListener;
}
void run() {
// real code has a large stream pipeline here
fooListener.someChangesHappened(Stream.of(Changes.ONE, Changes.TWO, Changes.THREE));
}
}
}
Using Java 21, JUnit 5, Mockito 5, and Google Truth.
Upvotes: 1
Reputation: 5751
Assuming you are just verifying the argument to a mock implementation, and are not actually using it, here is a custom Hamcrest Matcher
that will get the job done. It gets hairy when you need to read from the Stream
more than once, because Stream
s are not built for that. You'll notice that this solution even needs to protect itself from JUnit calling matches
more than once.
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class Foo {
@Mock
FooListener fooListener;
@Before
public void happen() {
fooListener.someChangesHappened(Stream.of(Changes.ONE, Changes.TWO, Changes.THREE));
}
@Test
public void contains() {
verify(fooListener).someChangesHappened(argThat(streamThat(hasItem(Changes.TWO))));
}
@Test
public void doesNotContain() {
verify(fooListener).someChangesHappened(argThat(streamThat(not(hasItem(Changes.FOUR)))));
}
private static <T> Matcher<Stream<T>> streamThat(Matcher<Iterable<? super T>> toMatch) {
return new IterableStream<>(toMatch);
}
private interface FooListener {
void someChangesHappened(Stream<Changes> stream);
}
private enum Changes {
ONE, TWO, THREE, FOUR
}
private static class IterableStream<T> extends TypeSafeMatcher<Stream<T>> {
Matcher<Iterable<? super T>> toMatch;
List<T> input = null;
public IterableStream(Matcher<Iterable<? super T>> toMatch) {
this.toMatch = toMatch;
}
@Override
protected synchronized boolean matchesSafely(Stream<T> item) {
// This is to protect against JUnit calling this more than once
input = input == null ? item.collect(Collectors.toList()) : input;
return toMatch.matches(input);
}
@Override
public void describeTo(Description description) {
description.appendText("stream that represents ");
toMatch.describeTo(description);
}
}
}
Upvotes: 6