Doradus
Doradus

Reputation: 1036

How do I use Mockito to test that a Java 8 Stream had the expected values?

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

Answers (2)

wilmol
wilmol

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

Yosef Weiner
Yosef Weiner

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 Streams 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

Related Questions