Search code examples
javaspring-webfluxproject-reactor

How does StepVerifier's "recording" feature work?


Why does this test fail?

import org.junit.jupiter.api.Test;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

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

public class StepVerifierTest {
    @Test
    void test() {
        List<GrantedAuthority> roles = List.of(
                new SimpleGrantedAuthority("user"),
                new SimpleGrantedAuthority("admin")
        );
        StepVerifier.create(Flux.fromIterable(roles).map(GrantedAuthority.class::cast))
                .recordWith(ArrayList::new)
                .expectRecordedMatches(actualRoles -> actualRoles.containsAll(roles))
                .expectComplete()
                .verify();
    }
}
java.lang.AssertionError: expectation "expectRecordedMatches" failed (expected collection predicate match; actual: [user])

For some reason, StepVerifier put only the first element in the collection and then rushed to check the predicate. The documentation says

Start a recording session storing Subscriber.onNext(Object) values in the supplied Collection.

My expectation was that StepVerifier would poll elements till the stream is exhausted and only then check the predicate

StepVerifier's API doesn't suggest I should manually "record" emitted elements

Note please that this question is not about "how do I make the test pass". I know how

    @Test
    void test() {
        List<GrantedAuthority> roles = List.of(
                new SimpleGrantedAuthority("user"),
                new SimpleGrantedAuthority("admin")
        );
        StepVerifier.create(Flux.fromIterable(roles).collectList())
                .expectNextMatches(actualRoles -> actualRoles.containsAll(roles))
                .expectComplete()
                .verify();

/*
This, on the other hand, won't do since it asserts not only 
on the content but also on the order of elements

        StepVerifier.create(Flux.fromIterable(roles))
                .expectNextSequence(roles)
                .expectComplete()
                .verify();
*/
    }

Instead, I'm curious how that "recording" feature works


Solution

  • Put a count expectation between recordWith() and expectRecordedMatches(). I figure it trggers those onNext() calls

        @Test
        void test() {
            List<GrantedAuthority> roles = List.of(
                    new SimpleGrantedAuthority("user"),
                    new SimpleGrantedAuthority("admin")
            );
            StepVerifier.create(Flux.fromIterable(roles).map(GrantedAuthority.class::cast))
                    .recordWith(ArrayList::new)
                    .expectNextCount(2)
                    .expectRecordedMatches(actualRoles -> actualRoles.containsAll(roles))
                    .expectComplete()
                    .verify();
        }
    

    Simone Basle's way, I reckon, would be to do something like this (the guy's former lead at Project Reactor):

            StepVerifier.create(Flux.fromIterable(roles).map(GrantedAuthority.class::cast))
                    .thenConsumeWhile(roles::contains)
                    .expectComplete()
                    .verify();
    

    However, it passes even if the Flux omits elements

            StepVerifier.create(Flux.fromIterable(roles).take(1))
                    .thenConsumeWhile(roles::contains)
                    .expectComplete()
                    .verify();