/** * Prepare a new {@code StepVerifier} in an uncontrolled environment: * {@link Step#thenAwait} will block in real time. * Each {@link #verify()} will fully (re)play the scenario. * The verification will request a specified amount of values. * * @param publisher the publisher to subscribe to and verify * @param n the amount of items to request * * @return a builder for expectation declaration and ultimately verification. */ static <T> FirstStep<T> create(Publisher<? extends T> publisher, long n) { return create(publisher, StepVerifierOptions.create().initialRequest(n)); }
@Test public void withInitialContext() { StepVerifier.create(Mono.subscriberContext(), StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar"))) .assertNext(c -> Assertions.assertThat(c.getOrDefault("foo", "baz")) .isEqualTo("bar")) .verifyComplete(); }
@Test public void withInitialContextAndContextAssertionsParents() { StepVerifier.create(Mono.just(1).map(i -> i + 10), //this causes the subscription to be resolvable to a chain of parents StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar"))) .expectAccessibleContext() .contains("foo", "bar") .then() .expectNext(11) .verifyComplete(); }
@Test public void withInitialContextButNoPropagation() { StepVerifier.create(Mono.just(1), //just(1) uses a ScalarSubscription which can't be resolved to a chain of parents StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar"))) .expectNoAccessibleContext() .expectNext(1) .verifyComplete(); }
@Test public void nextCompleteAndErrorHaveContext() { Context context = Context.of("foo", "bar"); List<Signal> signals = new ArrayList<>(); StepVerifier.create(Flux.just("hello") .doOnEach(signals::add), StepVerifierOptions.create().withInitialContext(context)) .expectNext("hello") .verifyComplete(); assertThat(signals) .allSatisfy(signal -> assertThat(signal.getContext().hasKey("foo")) .as("has Context value") .isTrue()); }
@Test public void testDurationFailureWithScenarioName() { StepVerifierOptions options = StepVerifierOptions.create() .scenarioName("some scenario name"); StepVerifier stepVerifier = StepVerifier .create(Mono.delay(Duration.ofMillis(100)), options) .expectNextCount(1) .expectComplete(); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> stepVerifier.verify(Duration.ofMillis(10))) .withMessageStartingWith("[some scenario name] VerifySubscriber timed out on reactor.core.publisher.MonoDelay$MonoDelayRunnable@"); }
@Test public void contextDiscardCaptureWithInitialContext() { Context initial = Context.of("foo", "bar"); StepVerifier.create(Mono.subscriberContext() .flatMapIterable(ctx -> ctx.stream() .map(Map.Entry::getKey) .map(String::valueOf) .collect(Collectors.toList()) ).concatWithValues("A", "B") .filter(s -> s.length() > 1) , StepVerifierOptions.create().withInitialContext(initial)) .expectNext("foo") .expectNext("reactor.onDiscard.local") .expectComplete() .verifyThenAssertThat() .hasDiscardedExactly("A", "B"); }
@Test public void expectAccessibleContextWithInitialContext() { StepVerifierOptions stepVerifierOptions = StepVerifierOptions.create() .withInitialContext(Context.of("foo", "bar")); StepVerifier.create(Mono.just(1), stepVerifierOptions) .expectAccessibleContext() .contains("foo", "bar") .then() .expectNext(1) .verifyComplete(); }
@Test public void suppliedVirtualTimeButNoSourceDoesntEnableScheduler() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); new DefaultStepVerifierBuilder<String>(StepVerifierOptions.create() .initialRequest(Long.MAX_VALUE) .virtualTimeSchedulerSupplier(() -> vts), null) //important to avoid triggering of vts capture-and-enable .expectNoEvent(Duration.ofSeconds(4)) .expectComplete() .toSubscriber(); try { //also test the side effect case where VTS has been enabled and not reset VirtualTimeScheduler current = VirtualTimeScheduler.get(); assertThat(current).isNotSameAs(vts); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("VirtualTimeScheduler"); } }
@Test public void contextAccessibleWithEmptySubscriptionAndOperator2() { StepVerifier.create(Flux.empty() .map(i -> i), StepVerifierOptions.create().withInitialContext(Context.of("a", "b"))) .expectAccessibleContext() .contains("a", "b") .then() .verifyComplete(); }
@Test public void discardOnFlushWithoutRequest() { TestPublisher<Integer> testPublisher = TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); StepVerifier.create(testPublisher .flux() .bufferTimeout(10, Duration.ofMillis(200)), StepVerifierOptions.create().initialRequest(0)) .then(() -> testPublisher.emit(1, 2, 3)) .thenAwait(Duration.ofMillis(250)) .expectErrorMatches(Exceptions::isOverflow) .verifyThenAssertThat() .hasDiscardedExactly(1, 2, 3); }
@Test public void contextNotAccessibleWithEmptySubscriptionOnly() { StepVerifier.create(Flux.empty(), StepVerifierOptions.create().withInitialContext(Context.of("a", "b"))) .expectNoAccessibleContext() .verifyComplete(); } }
@Test(timeout = 1000L) public void lowRequestCheckCanBeDisabled() { StepVerifier.create(Flux.just(1, 2), StepVerifierOptions.create().initialRequest(1).checkUnderRequesting(false)) .expectNext(1) .thenConsumeWhile(s -> s == 1); //don't verify, this alone would throw an exception if check activated }
@Test public void recursiveCountdownBackpressure() { StepVerifier.create(Mono.just(10) .expand(countDown), StepVerifierOptions.create() .initialRequest(0) .checkUnderRequesting(false)) .thenRequest(1) .expectNext(10) .thenRequest(3) .expectNext(9, 8, 7) .thenRequest(4) .expectNext(6, 5, 4, 3) .thenRequest(3) .expectNext(2, 1, 0) .verifyComplete(); }
@Test public void discardOnEmitOverflow() { final TestPublisher<Integer> publisher = TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); StepVerifier.create(publisher.flux() .buffer(Mono.never()), StepVerifierOptions.create().initialRequest(0)) .then(() -> publisher.emit(1, 2, 3)) .expectErrorMatches(Exceptions::isOverflow) .verifyThenAssertThat() .hasDiscardedExactly(1, 2, 3); } }
@Test public void recursiveCountdownBackpressureDepth() { StepVerifier.create(Mono.just(10) .expandDeep(countDown), StepVerifierOptions.create() .initialRequest(0) .checkUnderRequesting(false)) .thenRequest(1) .expectNext(10) .thenRequest(3) .expectNext(9, 8, 7) .thenRequest(4) .expectNext(6, 5, 4, 3) .thenRequest(3) .expectNext(2, 1, 0) .verifyComplete(); }
@Test public void subscribedTwice() { Flux<String> flux = Flux.just("foo", "bar"); DefaultVerifySubscriber<String> s = new DefaultStepVerifierBuilder<String>(StepVerifierOptions.create().initialRequest(Long.MAX_VALUE), null) .expectNext("foo", "bar") .expectComplete() .toSubscriber(); flux.subscribe(s); flux.subscribe(s); assertThatExceptionOfType(AssertionError.class) .isThrownBy(s::verify) .withMessageStartingWith("expectation failed (an unexpected Subscription has been received"); }
@Test public void recursiveCountdownBackpressure() { StepVerifier.create(Flux.just(10) .expand(countDown), StepVerifierOptions.create() .initialRequest(0) .checkUnderRequesting(false)) .thenRequest(1) .expectNext(10) .thenRequest(3) .expectNext(9, 8, 7) .thenRequest(4) .expectNext(6, 5, 4, 3) .thenRequest(3) .expectNext(2, 1, 0) .verifyComplete(); }
@Test public void recursiveCountdownBackpressureDepth() { StepVerifier.create(Flux.just(10) .expandDeep(countDown), StepVerifierOptions.create() .initialRequest(0) .checkUnderRequesting(false)) .thenRequest(1) .expectNext(10) .thenRequest(3) .expectNext(9, 8, 7) .thenRequest(4) .expectNext(6, 5, 4, 3) .thenRequest(3) .expectNext(2, 1, 0) .verifyComplete(); }
@Test public void testWithDescriptionAndScenarioName() { StepVerifierOptions options = StepVerifierOptions.create() .initialRequest(3) .scenarioName("some scenario name"); StepVerifier stepVerifier = StepVerifier .create(Flux.just("foo", "bar", "baz"), options) .expectNext("foo") .as("first") .expectNext("bar") .as("second") .expectNext("bar") .as("third") .as("this is ignored") .expectComplete() .log(); assertThatExceptionOfType(AssertionError.class) .isThrownBy(stepVerifier::verify) .withMessage("[some scenario name] expectation \"third\" failed (expected value: bar; actual value: baz)"); }