reactor.test.StepVerifier Maven / Gradle / Ivy
/*
* Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package reactor.test;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Fuseable;
import reactor.core.publisher.Hooks;
import reactor.core.scheduler.Schedulers;
import reactor.test.scheduler.VirtualTimeScheduler;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
import reactor.util.function.Tuple2;
/**
* A {@link StepVerifier} provides a declarative way of creating a verifiable script for
* an async {@link Publisher} sequence, by expressing expectations about the events that
* will happen upon subscription. The verification must be triggered after the terminal
* expectations (completion, error, cancellation) have been declared, by calling one of
* the {@link #verify()} methods.
*
*
* - Create a {@code StepVerifier} around a {@link Publisher} using
* {@link #create create(Publisher)} or
* {@link #withVirtualTime withVirtualTime(Supplier<Publisher>)}
* (in which case you should lazily create the publisher inside the provided
* {@link Supplier lambda}).
* - Set up individual value expectations using
* {@link Step#expectNext expectNext},
* {@link Step#expectNextMatches(Predicate) expectNextMatches(Predicate)},
* {@link Step#assertNext(Consumer) assertNext(Consumer)},
* {@link Step#expectNextCount(long) expectNextCount(long)} or
* {@link Step#expectNextSequence(Iterable) expectNextSequence(Iterable)}.
* - Trigger subscription actions during the verification using either
* {@link Step#thenRequest(long) thenRequest(long)} or {@link Step#thenCancel() thenCancel()}.
* - Finalize the test scenario using a terminal expectation:
* {@link LastStep#expectComplete expectComplete()},
* {@link LastStep#expectError expectError()},
* {@link LastStep#expectError(Class) expectError(Class)},
* {@link LastStep#expectErrorMatches(Predicate) expectErrorMatches(Predicate)}, or
* {@link LastStep#thenCancel thenCancel()}.
* - Trigger the verification of the resulting {@code StepVerifier} on its {@code Publisher}
* using either {@link #verify()} or {@link #verify(Duration)}. (note some of the terminal
* expectations above have a "verify" prefixed alternative that both declare the
* expectation and trigger the verification).
* - If any expectations failed, an {@link AssertionError} will be thrown indicating the
* failures.
*
*
* For example:
*
* StepVerifier.create(Flux.just("foo", "bar"))
* .expectNext("foo")
* .expectNext("bar")
* .expectComplete()
* .verify();
*
*
* @author Arjen Poutsma
* @author Stephane Maldini
* @author Simon Baslé
*/
public interface StepVerifier {
/**
* Default verification timeout (see {@link #verify()}) is "no timeout".
*
* @see #setDefaultTimeout(Duration)
* @see #resetDefaultTimeout()
*/
Duration DEFAULT_VERIFY_TIMEOUT = Duration.ZERO;
/**
* Set the {@link #verify()} timeout for all {@link StepVerifier} created through the
* factory methods ({@link #create(Publisher)}, {@link #withVirtualTime(Supplier)}, etc.).
*
* This affects ALL such verifiers created after this call, until a call to either
* this method or {@link #resetDefaultTimeout()}.
*
* @param timeout the timeout to use for {@link #verify()} calls on all {@link StepVerifier}
* created through the factory methods after this call. {@literal null} is interpreted
* as a call to {@link #resetDefaultTimeout()}.
*/
static void setDefaultTimeout(@Nullable Duration timeout) {
DefaultStepVerifierBuilder.defaultVerifyTimeout =
timeout == null ? DEFAULT_VERIFY_TIMEOUT : timeout;
}
/**
* Reset the {@link #verify()} timeout to the "unlimited" default.
*
* This affects ALL such verifiers created after this call, until a call to
* {@link #setDefaultTimeout(Duration)}.
*/
static void resetDefaultTimeout() {
setDefaultTimeout(null);
}
/**
* 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.
*
* @param publisher the publisher to subscribe to and verify
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep create(Publisher extends T> publisher) {
return create(publisher, Long.MAX_VALUE);
}
/**
* 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 FirstStep create(Publisher extends T> publisher, long n) {
return create(publisher, StepVerifierOptions.create().initialRequest(n));
}
/**
* 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 according to
* the {@link StepVerifierOptions options} passed.
*
* @param publisher the publisher to subscribe to
* @param options the options for the verification
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep create(Publisher extends T> publisher, StepVerifierOptions options) {
return DefaultStepVerifierBuilder.newVerifier(options, () -> publisher);
}
/**
* Prepare a new {@code StepVerifier} in a controlled environment using
* {@link VirtualTimeScheduler} to manipulate a virtual clock via
* {@link Step#thenAwait}. The scheduler is injected into all {@link Schedulers} factories,
* which means that any operator created within the lambda without a specific scheduler
* will use virtual time.
* Each {@link #verify()} will fully (re)play the scenario.
* The verification will request an unbounded amount of values.
*
* Note that virtual time, {@link Step#thenAwait(Duration)} sources that are
* subscribed on a different {@link reactor.core.scheduler.Scheduler} (eg. a source
* that is initialized outside of the lambda with a dedicated Scheduler) and
* delays introduced within the data path (eg. an interval in a flatMap) are not
* always compatible, as this can perform the clock move BEFORE the interval schedules
* itself, resulting in the interval never playing out.
*
* @param scenarioSupplier a mandatory supplier of the {@link Publisher} to subscribe
* to and verify. In order for operators to use virtual time, they must be invoked
* from within the lambda.
* @param the type of the subscriber
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep withVirtualTime(Supplier extends Publisher extends T>> scenarioSupplier) {
return withVirtualTime(scenarioSupplier, Long.MAX_VALUE);
}
/**
* Prepare a new {@code StepVerifier} in a controlled environment using
* {@link VirtualTimeScheduler} to manipulate a virtual clock via
* {@link Step#thenAwait}. The scheduler is injected into all {@link Schedulers} factories,
* which means that any operator created within the lambda without a specific scheduler
* will use virtual time.
* Each {@link #verify()} will fully (re)play the scenario.
* The verification will request a specified amount of values.
*
* Note that virtual time, {@link Step#thenAwait(Duration)} sources that are
* subscribed on a different {@link reactor.core.scheduler.Scheduler} (eg. a source
* that is initialized outside of the lambda with a dedicated Scheduler) and
* delays introduced within the data path (eg. an interval in a flatMap) are not
* always compatible, as this can perform the clock move BEFORE the interval schedules
* itself, resulting in the interval never playing out.
*
* @param scenarioSupplier a mandatory supplier of the {@link Publisher} to subscribe
* to and verify. In order for operators to use virtual time, they must be invoked
* from within the lambda.
* @param n the amount of items to request (must be >= 0)
* @param the type of the subscriber
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep withVirtualTime(Supplier extends Publisher extends T>> scenarioSupplier,
long n) {
return withVirtualTime(scenarioSupplier, () -> VirtualTimeScheduler.getOrSet(true), n);
}
/**
* Prepare a new {@code StepVerifier} in a controlled environment using
* a user-provided {@link VirtualTimeScheduler} to manipulate a virtual clock via
* {@link Step#thenAwait}. The scheduler is injected into all {@link Schedulers} factories,
* which means that any operator created within the lambda without a specific scheduler
* will use virtual time.
* Each {@link #verify()} will fully (re)play the scenario.
* The verification will request a specified amount of values.
*
* Note that virtual time, {@link Step#thenAwait(Duration)} sources that are
* subscribed on a different {@link reactor.core.scheduler.Scheduler} (eg. a source
* that is initialized outside of the lambda with a dedicated Scheduler) and
* delays introduced within the data path (eg. an interval in a flatMap) are not
* always compatible, as this can perform the clock move BEFORE the interval schedules
* itself, resulting in the interval never playing out.
*
* @param scenarioSupplier a mandatory supplier of the {@link Publisher} to subscribe
* to and verify. In order for operators to use virtual time, they must be invoked
* from within the lambda.
* @param vtsLookup the supplier of the {@link VirtualTimeScheduler} to inject and
* manipulate during verification.
* @param n the amount of items to request (must be >= 0)
* @param the type of the subscriber
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep withVirtualTime(
Supplier extends Publisher extends T>> scenarioSupplier,
Supplier extends VirtualTimeScheduler> vtsLookup,
long n) {
return withVirtualTime(scenarioSupplier, StepVerifierOptions.create()
.initialRequest(n)
.virtualTimeSchedulerSupplier(vtsLookup));
}
/**
* Prepare a new {@code StepVerifier} in a controlled environment using
* a user-provided {@link VirtualTimeScheduler} to manipulate a virtual clock via
* {@link Step#thenAwait}. The scheduler is injected into all {@link Schedulers} factories,
* which means that any operator created within the lambda without a specific scheduler
* will use virtual time.
* Each {@link #verify()} will fully (re)play the scenario.
* The verification will request a specified amount of values according to
* the provided {@link StepVerifierOptions options}.
*
* If no {@link VirtualTimeScheduler} {@link Supplier} is set in the options, this
* method will make a {@link StepVerifierOptions#copy() copy} of said options and
* set up the default supplier (like the one in {@link #withVirtualTime(Supplier)}).
*
* Note that virtual time, {@link Step#thenAwait(Duration)} sources that are
* subscribed on a different {@link reactor.core.scheduler.Scheduler} (eg. a source
* that is initialized outside of the lambda with a dedicated Scheduler) and
* delays introduced within the data path (eg. an interval in a flatMap) are not
* always compatible, as this can perform the clock move BEFORE the interval schedules
* itself, resulting in the interval never playing out.
*
* @param scenarioSupplier a mandatory supplier of the {@link Publisher} to subscribe
* to and verify. In order for operators to use virtual time, they must be invoked
* from within the lambda.
* @param options the verification options, including the supplier of the
* {@link VirtualTimeScheduler} to inject and manipulate during verification
* (see note above in case options doesn't define such a supplier)
* @param the type of the subscriber
*
* @return a builder for expectation declaration and ultimately verification.
*/
static FirstStep withVirtualTime(
Supplier extends Publisher extends T>> scenarioSupplier,
StepVerifierOptions options) {
DefaultStepVerifierBuilder.checkPositive(options.getInitialRequest());
Objects.requireNonNull(scenarioSupplier, "scenarioSupplier");
//force the default VTS supplier if the provided options doesn't define a VTS supplier.
//note we make a copy just in case the original options are reused.
if (options.getVirtualTimeSchedulerSupplier() == null) {
options = options
.copy()
.virtualTimeSchedulerSupplier(() -> VirtualTimeScheduler.getOrSet(true));
}
return DefaultStepVerifierBuilder.newVerifier(options, scenarioSupplier);
}
/**
* Activate debug logging of a description of the test scenario, as well as
* some details about certain verification steps.
*
* @return the verifier for final {@link #verify()} call
*/
StepVerifier log();
/**
* Trigger the subscription and prepare for verifications but doesn't block. Calling one
* of the {@link #verify()} methods afterwards will block until the sequence is validated
* and throw if assertions fail.
*
* Calling this method more than once in a row should be a NO-OP, returning the same
* instance as the first call.
*
* @return a {@link StepVerifier} that is in progress but on which one can chose to block later.
*/
StepVerifier verifyLater();
/**
* Verify the signals received by this subscriber. Unless a default timeout has been
* set before construction of the {@link StepVerifier} via {@link StepVerifier#setDefaultTimeout(Duration)},
* this method will block until the stream has been terminated
* (either through {@link Subscriber#onComplete()}, {@link Subscriber#onError(Throwable)} or
* {@link Subscription#cancel()}). Depending on the declared expectations and actions,
* notably in case of undersized manual requests, such a verification could also block
* indefinitely.
*
* @return the actual {@link Duration} the verification took.
* @throws AssertionError in case of expectation failures
* @see #verify(Duration)
* @see #setDefaultTimeout(Duration)
*/
Duration verify() throws AssertionError;
/**
* Verify the signals received by this subscriber. This method will
* block for up to the given duration or until the stream has been
* terminated (either through {@link Subscriber#onComplete()},
* {@link Subscriber#onError(Throwable)} or {@link Subscription#cancel()}). Use
* {@link Duration#ZERO} for an unlimited wait for termination.
*
* @param duration the maximum duration to wait for the sequence to terminate, or
* {@link Duration#ZERO} for unlimited wait.
* @return the actual {@link Duration} the verification took.
* @throws AssertionError in case of expectation failures, or when the verification
* times out
*/
Duration verify(Duration duration) throws AssertionError;
/**
* {@link #verify() Verifies} the signals received by this subscriber, then exposes
* various {@link Assertions assertion methods} on the final state.
*
* Note that like {@link #verify()}, this method will block until
* the stream has been terminated (either through {@link Subscriber#onComplete()},
* {@link Subscriber#onError(Throwable)} or {@link Subscription#cancel()}).
* Depending on the declared expectations and actions, notably in case of undersized
* manual requests, such a verification could also block indefinitely. Use
* {@link #setDefaultTimeout(Duration)} to globally add a timeout on verify()-derived
* methods.
*
* @return the actual {@link Duration} the verification took.
* @throws AssertionError in case of expectation failures
*/
Assertions verifyThenAssertThat();
/**
* {@link #verify() Verifies} the signals received by this subscriber, then exposes
* various {@link Assertions assertion methods} on the final state.
*
* Note that like {@link #verify()}, this method will block until
* the stream has been terminated (either through {@link Subscriber#onComplete()},
* {@link Subscriber#onError(Throwable)} or {@link Subscription#cancel()}).
* Depending on the declared expectations and actions, notably in case of undersized
* manual requests, such a verification could also block indefinitely. As a consequence
* you can use the {@link Duration} {@code duration} parameter to set a timeout.
*
* @param duration the maximum duration to wait for the sequence to terminate, or
* {@link Duration#ZERO} for unlimited wait.
* @return {@link Assertions} for chaining post-verification state assertions
*/
Assertions verifyThenAssertThat(Duration duration);
/**
* Define a builder for terminal states.
*/
interface LastStep {
/**
* Expect an error and consume with the given consumer. Any
* {@code AssertionError}s thrown by the consumer will be rethrown
* during {@linkplain #verify() verification}.
*
* @param consumer the consumer for the exception
*
* @return the built verification
*/
StepVerifier consumeErrorWith(Consumer consumer);
/**
* Expect an unspecified error.
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onError(Throwable)
*/
StepVerifier expectError();
/**
* Expect an error of the specified type.
*
* @param clazz the expected error type
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onError(Throwable)
*/
StepVerifier expectError(Class extends Throwable> clazz);
/**
* Expect an error with the specified message.
*
* @param errorMessage the expected error message
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onError(Throwable)
*/
StepVerifier expectErrorMessage(String errorMessage);
/**
* Expect an error and evaluate with the given predicate.
*
* @param predicate the predicate to test on the next received error
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onError(Throwable)
*/
StepVerifier expectErrorMatches(Predicate predicate);
/**
* Expect an error and assert it via assertion(s) provided as a {@link Consumer}.
* Any {@link AssertionError} thrown by the consumer has its message propagated
* through a new StepVerifier-specific AssertionError.
*
* @param assertionConsumer the consumer that applies assertion(s) on the next received error
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onError(Throwable)
*/
StepVerifier expectErrorSatisfies(Consumer assertionConsumer);
/**
* Verify that the {@link Publisher} under test doesn't terminate but
* rather times out after the provided {@link Duration} (a timeout implying
* a cancellation of the source).
*
* This is equivalent to appending the {@link reactor.core.publisher.Flux#timeout(Duration) timeout}
* operator to the publisher and expecting a {@link java.util.concurrent.TimeoutException}
* {@link #expectError(Class) onError signal}, while also triggering a {@link Step#thenAwait(Duration) wait}
* to ensure unexpected signals are detected.
*
* @param duration the {@link Duration} for which no new event is expected
* @return the built verification scenario, ready to be verified
*/
StepVerifier expectTimeout(Duration duration);
/**
* Expect the completion signal.
*
* @return the built verification scenario, ready to be verified
*
* @see Subscriber#onComplete()
*/
StepVerifier expectComplete();
/**
* Cancel the underlying subscription. This happens sequentially after the
* previous step.
*
* Note that time-manipulating operators like {@link Step#expectNoEvent(Duration)}
* are detected and waited for before cancellation occurs.
*
* @return the built verification scenario, ready to be verified
*
* @see Subscription#cancel()
*/
StepVerifier thenCancel();
/**
* Trigger the {@link #verify() verification}, expecting an unspecified error
* as terminal event.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @return the actual {@link Duration} the verification took.
*
* @see #expectError()
* @see #verify()
* @see Subscriber#onError(Throwable)
*/
Duration verifyError();
/**
* Trigger the {@link #verify() verification}, expecting an error of the specified
* type as terminal event.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @param clazz the expected error type
* @return the actual {@link Duration} the verification took.
*
* @see #expectError(Class)
* @see #verify()
* @see Subscriber#onError(Throwable)
*/
Duration verifyError(Class extends Throwable> clazz);
/**
* Trigger the {@link #verify() verification}, expecting an error with the
* specified message as terminal event.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @param errorMessage the expected error message
* @return the actual {@link Duration} the verification took.
*
* @see #expectErrorMessage(String)
* @see #verify()
* @see Subscriber#onError(Throwable)
*/
Duration verifyErrorMessage(String errorMessage);
/**
* Trigger the {@link #verify() verification}, expecting an error that matches
* the given predicate as terminal event.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @param predicate the predicate to test on the next received error
* @return the actual {@link Duration} the verification took.
*
* @see #expectErrorMatches(Predicate)
* @see #verify()
* @see Subscriber#onError(Throwable)
*/
Duration verifyErrorMatches(Predicate predicate);
/**
* Trigger the {@link #verify() verification}, expecting that the {@link Publisher}
* under test doesn't terminate but rather times out after the provided {@link Duration}.
* A timeout implies a cancellation of the source.
*
* This is a convenience method that calls {@link #verify()} in addition to {@link #expectTimeout(Duration)}.
* The later is equivalent to appending the {@link reactor.core.publisher.Flux#timeout(Duration) timeout}
* operator to the publisher and expecting a {@link java.util.concurrent.TimeoutException}
* {@link #verifyError(Class) onError signal}, while also triggering a {@link Step#thenAwait(Duration) wait}
* to ensure unexpected signals are detected.
*
* @param duration the {@link Duration} for which no new event is expected
* @return the actual {@link Duration} the verification took.
*
* @see #expectTimeout(Duration)
* @see #verify()
*/
default Duration verifyTimeout(Duration duration) {
return expectTimeout(duration).verify();
}
/**
* Trigger the {@link #verify() verification}, expecting an error as terminal event
* which gets asserted via assertion(s) provided as a {@link Consumer}.
* Any {@link AssertionError} thrown by the consumer has its message propagated
* through a new StepVerifier-specific AssertionError.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @param assertionConsumer the consumer that applies assertion(s) on the next received error
*
* @return the actual {@link Duration} the verification took.
*
* @see #expectErrorSatisfies(Consumer)
* @see #verify()
* @see Subscriber#onError(Throwable)
*/
Duration verifyErrorSatisfies(Consumer assertionConsumer);
/**
* Trigger the {@link #verify() verification}, expecting a completion signal
* as terminal event.
*
* This is a convenience method that calls {@link #verify()} in addition to the
* expectation. Explicitly use the expect method and verification method
* separately if you need something more specific (like activating logging or
* changing the default timeout).
*
* @return the actual {@link Duration} the verification took.
*
* @see #expectComplete()
* @see #verify()
* @see Subscriber#onComplete()
*
*/
Duration verifyComplete();
}
/**
* Define a builder for expecting main sequence individual signals.
*
* @param the type of values that the subscriber contains
*/
interface Step extends LastStep {
/**
* Set a description for the previous verification step. Choosing
* a unique and descriptive name can make assertion errors easier to
* resolve.
*
* Note that calling this several times in a row will only take the
* first description into account.
*
* @param description the description for the previous verification step
* @return this builder
*/
Step as(String description);
/**
* Expect an element and consume with the given consumer.Any {@code
* AssertionError}s thrown by the consumer will be rethrown during {@linkplain
* #verify() verification}.
*
* @param consumer the consumer for the value
*
* @return this builder
*/
Step consumeNextWith(Consumer super T> consumer);
/**
* Expect an element and consume it with the given consumer, usually performing
* assertions on it (eg. using Hamcrest, AssertJ or JUnit assertion methods).
* Alias for {@link #consumeNextWith(Consumer)} for better discoverability of
* that use case.
*
* Any {@code AssertionError}s thrown by the consumer will be rethrown during
* {@linkplain #verify() verification}.
*
* @param assertionConsumer the consumer for the value, performing assertions
* @return this builder
*/
default Step assertNext(Consumer super T> assertionConsumer) {
return consumeNextWith(assertionConsumer);
}
/**
* Expect a recording session started via {@link #recordWith}, end it and verify
* it by applying the given consumer.
* Any {@code AssertionError}s thrown by the consumer will be rethrown during
* {@linkplain #verify() verification}.
*
* @param consumer the consumer used to apply assertions on the recorded session
*
* @return this builder
*/
Step consumeRecordedWith(Consumer super Collection> consumer);
/**
* Expect the next element received to be equal to the given value.
*
* @param t the value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t);
/**
* Expect the next elements received to be equal to the given values.
*
* @param t1 the first value to expect
* @param t2 the second value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t1, T t2);
/**
* Expect the next elements received to be equal to the given values.
*
* @param t1 the first value to expect
* @param t2 the second value to expect
* @param t3 the third value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t1, T t2, T t3);
/**
* Expect the next elements received to be equal to the given values.
*
* @param t1 the first value to expect
* @param t2 the second value to expect
* @param t3 the third value to expect
* @param t4 the fourth value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t1, T t2, T t3, T t4);
/**
* Expect the next elements received to be equal to the given values.
*
* @param t1 the first value to expect
* @param t2 the second value to expect
* @param t3 the third value to expect
* @param t4 the fourth value to expect
* @param t5 the fifth value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t1, T t2, T t3, T t4, T t5);
/**
* Expect the next elements received to be equal to the given values.
*
* @param t1 the first value to expect
* @param t2 the second value to expect
* @param t3 the third value to expect
* @param t4 the fourth value to expect
* @param t5 the fifth value to expect
* @param t6 the sixth value to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNext(T t1, T t2, T t3, T t4, T t5, T t6);
/**
* Expect the next elements received to be equal to the given values.
*
* @param ts the values to expect
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
@SuppressWarnings("unchecked")
Step expectNext(T... ts);
/**
* Expect to received {@code count} elements, starting from the previous
* expectation or onSubscribe.
*
* @param count the number of emitted items to expect.
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNextCount(long count);
/**
* Expect the next elements to match the given {@link Iterable} until its
* iterator depletes.
*
* @param iterable the iterable containing the next expected values
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNextSequence(Iterable extends T> iterable);
/**
* Expect an element and evaluate with the given predicate.
*
* @param predicate the predicate to test on the next received value
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectNextMatches(Predicate super T> predicate);
/**
* Expect a {@link Subscription} and consume with the given consumer. Any {@code
* AssertionError}s thrown by the consumer will be rethrown during {@linkplain
* #verify() verification}.
*
* @param consumer the consumer for the {@link Subscription}
*
* @return this builder
*
* @see Subscriber#onSubscribe(Subscription)
*/
Step consumeSubscriptionWith(Consumer super Subscription> consumer);
/**
* Expect that after the {@link Subscription} step, a {@link Context} has been
* propagated. You can continue with assertions on said {@link Context}, and you
* will need to use {@link ContextExpectations#then()} to switch back to verifying
* the sequence itself.
*
* @return a {@link ContextExpectations} for further assertion of the propagated {@link Context}
*/
ContextExpectations expectAccessibleContext();
/**
* Expect that NO {@link Context} was propagated after the {@link Subscription}
* phase, which usually indicates that the sequence under test doesn't contain
* Reactor operators (i.e. external Publisher, just a scalar source...).
*
* @return this builder for further assertion of the sequence
*/
Step expectNoAccessibleContext();
/**
* Expect that no event has been observed by the verifier for the length of
* the provided {@link Duration}. If virtual time is used, this duration is
* verified using the virtual clock.
*
* Note that you should only use this method as the first expectation if you
* actually don't expect a subscription to happen. Use
* {@link FirstStep#expectSubscription()} combined with {@link Step#expectNoEvent(Duration)}
* to work around that.
*
* Also avoid using this method at the end of the set of expectations:
* prefer {@link #expectTimeout(Duration)} rather than {@code expectNoEvent(...).thenCancel()}.
*
* @param duration the duration for which to observe no event has been received
*
* @return this builder
*
* @see Subscriber
*/
Step expectNoEvent(Duration duration);
/**
* Expect a recording session started via {@link #recordWith}, end it and verify
* it by ensuring the provided predicate matches.
*
* @param predicate the predicate to test on the recorded session
*
* @return this builder
*
* @see Subscriber#onNext(Object)
*/
Step expectRecordedMatches(Predicate super Collection> predicate);
/**
* Start a recording session storing {@link Subscriber#onNext(Object)} values in
* the supplied {@link Collection}. Further steps
* {@link #expectRecordedMatches(Predicate)} and
* {@link #consumeRecordedWith(Consumer)} can consume and assert the session.
* If an existing recording session hasn't not been declaratively consumed, this step
* will override the current session.
*
* @param supplier the supplier for the {@link Collection} to use for recording.
*
* @return this builder
*/
Step recordWith(Supplier extends Collection> supplier);
/**
* Run an arbitrary task scheduled after previous expectations or tasks.
*
* @param task the task to run
*
* @return this builder
*/
Step then(Runnable task);
/**
* Mark a Pause in the expectation evaluation.
* If a {@link VirtualTimeScheduler} has been configured,
* {@link VirtualTimeScheduler#advanceTime()} will be used and the
* pause will not block testing or {@link Publisher} thread.
*
* @return this builder
*/
default Step thenAwait() {
return thenAwait(Duration.ZERO);
}
/**
* Pause the expectation evaluation for a given {@link Duration}.
* If a {@link VirtualTimeScheduler} has been configured,
* {@link VirtualTimeScheduler#advanceTimeBy(Duration)} will be used and the
* pause will not block testing or {@link Publisher} thread.
*
* @param timeshift a pause {@link Duration}
*
* @return this builder
*/
Step thenAwait(Duration timeshift);
/**
* Consume further onNext signals as long as they match a predicate.
*
* @param predicate the condition to continue consuming onNext
*
* @return this builder
*/
Step thenConsumeWhile(Predicate predicate);
/**
* Consume further onNext signals using a provided {@link Consumer} as long as
* they match a {@link Predicate}. You can use the consumer to apply assertions
* on each value.
*
* @param predicate the condition to continue consuming onNext
* @param consumer the consumer to use to consume the data, when the predicate
* matches
*
* @return this builder
*/
Step thenConsumeWhile(Predicate predicate, Consumer consumer);
/**
* Request the given amount of elements from the upstream {@code Publisher}. This
* is in addition to the initial number of elements requested by an
* initial passed demand like with {@link StepVerifier#create(Publisher, long)}.
*
* @param n the number of elements to request
*
* @return this builder
*
* @see Subscription#request(long)
*/
Step thenRequest(long n);
}
/**
* Define a builder for explicitly expecting an initializing {@link Subscription} as
* first signal.
*
* If {@link FirstStep} expectations are not used, the produced
* {@link StepVerifier} keeps a first expectation that will be checking if
* the first signal is a
* {@link Subscription}.
*
* @param the type of values that the subscriber contains
*/
interface FirstStep extends Step {
/**
* Provide a {@link Predicate} that will turn this StepVerifier's subscribers into
* {@link reactor.core.Fuseable.ConditionalSubscriber} and drive the {@link reactor.core.Fuseable.ConditionalSubscriber#tryOnNext(Object)}
* behavior of these subscribers. Set to {@code null} to deactivate (the default).
*
* @param tryOnNextPredicate the {@link Predicate} to use for conditional tryOnNext path
* @return the verifier for final {@link #verify()} call
*/
default FirstStep enableConditionalSupport(@Nullable Predicate super T> tryOnNextPredicate) {
if (tryOnNextPredicate != null) {
throw new UnsupportedOperationException("This implementation of StepVerifier doesn't support ConditionalSubscriber mode");
}
return this;
}
/**
* Expect the source {@link Publisher} to run with Reactor Fusion flow
* optimization. It will be requesting {@link Fuseable#ANY} fusion mode.
*
* @return this builder
*
* @see Fuseable
*/
Step expectFusion();
/**
* Expect the source {@link Publisher} to run the requested Reactor Fusion mode
* from any of these modes :
* {@link Fuseable#NONE}, {@link Fuseable#SYNC}, {@link Fuseable#ASYNC},
* {@link Fuseable#ANY}, {@link Fuseable#THREAD_BARRIER}.
*
* @param requested the requested and expected fusion mode
*
* @return this builder
*
* @see Fuseable
*/
Step expectFusion(int requested);
/**
* Expect the source {@link Publisher} to run with Reactor Fusion flow
* optimization.
* Expect the source {@link Publisher} to run the requested Reactor Fusion mode
* from any of these modes :
* {@link Fuseable#NONE}, {@link Fuseable#SYNC}, {@link Fuseable#ASYNC},
* {@link Fuseable#ANY}, {@link Fuseable#THREAD_BARRIER}.
*
* @param requested the requested fusion mode
* @param expected the expected fusion mode
*
* @return this builder
*
* @see Fuseable
*/
Step expectFusion(int requested, int expected);
/**
* Expect the source {@link Publisher} to NOT run with Reactor Fusion flow
* optimization. It will check if publisher is {@link Fuseable} or
* subscription is a {@link reactor.core.Fuseable.QueueSubscription}.
*
* @return this builder
*
* @see Fuseable
*/
Step expectNoFusionSupport();
/**
* Expect a {@link Subscription}.
* Effectively behave as the default implicit {@link Subscription} expectation.
*
* @return this builder
*
* @see Subscriber#onSubscribe(Subscription)
*/
Step expectSubscription();
/**
* Expect a {@link Subscription} and evaluate with the given predicate.
*
* @param predicate the predicate to test on the received {@link Subscription}
*
* @return this builder
*
* @see Subscriber#onSubscribe(Subscription)
*/
Step expectSubscriptionMatches(Predicate super Subscription> predicate);
}
/**
* Exposes post-verification state assertions.
*/
interface Assertions {
/**
* Assert that the tested publisher has dropped at least one element to the
* {@link Hooks#onNextDropped(Consumer)} hook.
*/
Assertions hasDroppedElements();
/**
* Assert that the tested publisher has not dropped any element to the
* {@link Hooks#onNextDropped(Consumer)} hook.
*/
Assertions hasNotDroppedElements();
/**
* Assert that the tested publisher has dropped at least all of the provided
* elements to the {@link Hooks#onNextDropped(Consumer)} hook, in any order.
*/
Assertions hasDropped(Object... values);
/**
* Assert that the tested publisher has dropped all of the provided elements to
* the {@link Hooks#onNextDropped(Consumer)} hook, in any order, and that no
* other elements were dropped.
*/
Assertions hasDroppedExactly(Object... values);
/**
* Assert that the tested publisher has discarded at least one element to the
* {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook.
*
* Unlike {@link #hasDroppedElements()}, the discard hook can be invoked as part
* of normal operations, eg. when an element doesn't match a filter.
*/
Assertions hasDiscardedElements();
/**
* Assert that the tested publisher has not discarded any element to the
* {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook.
*
* Unlike {@link #hasDroppedElements()}, the discard hook can be invoked as part
* of normal operations, eg. when an element doesn't match a filter.
*/
Assertions hasNotDiscardedElements();
/**
* Assert that the tested publisher has discarded at least all of the provided
* elements to the {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook, in any order.
*
* Unlike {@link #hasDroppedElements()}, the discard hook can be invoked as part
* of normal operations, eg. when an element doesn't match a filter.
*/
Assertions hasDiscarded(Object... values);
/**
* Assert that the tested publisher has discarded all of the provided elements to
* the {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook,
* in any order, and that no other elements were dropped.
*
* Unlike {@link #hasDroppedElements()}, the discard hook can be invoked as part
* of normal operations, eg. when an element doesn't match a filter.
*/
Assertions hasDiscardedExactly(Object... values);
/**
* Assert that the tested publisher has discarded one or more elements to the
* {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook,
* and check that the collection of discarded elements matches a predicate.
*/
Assertions hasDiscardedElementsMatching(Predicate> matcher);
/**
* Assert that the tested publisher has discarded one or more elements to the
* {@link reactor.core.publisher.Flux#doOnDiscard(Class, Consumer) discard} hook, and assert them as a collection.
*/
Assertions hasDiscardedElementsSatisfying(Consumer> consumer);
/**
* Assert that the tested publisher has dropped at least one error to the
* {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrors();
/**
* Assert that the tested publisher has not dropped any error to the
* {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasNotDroppedErrors();
/**
* Assert that the tested publisher has dropped exactly n errors to the
* {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrors(int n);
/**
* Assert that the tested publisher has dropped exactly one error of the given type
* to the {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrorOfType(Class extends Throwable> clazz);
/**
* Assert that the tested publisher has dropped exactly one error matching the given
* predicate to the {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrorMatching(Predicate matcher);
/**
* Assert that the tested publisher has dropped exactly one error with the exact provided
* message to the {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrorWithMessage(String message);
/**
* Assert that the tested publisher has dropped exactly one error with a message containing
* the provided string to the {@link Hooks#onErrorDropped(Consumer)} hook.
*/
Assertions hasDroppedErrorWithMessageContaining(String messagePart);
/**
* Assert that the tested publisher has dropped one or more errors to the
* {@link Hooks#onErrorDropped(Consumer)} hook, and assert them as a collection.
*/
Assertions hasDroppedErrorsSatisfying(Consumer> errorsConsumer);
/**
* Assert that the tested publisher has dropped one or more errors to the
* {@link Hooks#onErrorDropped(Consumer)} hook, and check that the collection of
* errors matches a predicate.
*/
Assertions hasDroppedErrorsMatching(Predicate> errorsConsumer);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* at least once.
*/
Assertions hasOperatorErrors();
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* exactly n times.
*/
Assertions hasOperatorErrors(int n);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* exactly once and the error is of the given type.
*/
Assertions hasOperatorErrorOfType(Class extends Throwable> clazz);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* exactly once and the error matches the given predicate.
*/
Assertions hasOperatorErrorMatching(Predicate matcher);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* exactly once and the error has the exact provided message.
*/
Assertions hasOperatorErrorWithMessage(String message);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* exactly once, with the error message containing the provided string.
*/
Assertions hasOperatorErrorWithMessageContaining(String messagePart);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* once or more, and assert the errors and optionally associated data as a collection.
*/
Assertions hasOperatorErrorsSatisfying(Consumer, Optional>>>> errorsConsumer);
/**
* Assert that the tested publisher has triggered the {@link Hooks#onOperatorError(BiFunction) onOperatorError} hook
* once or more, and check that the collection of errors and their optionally
* associated data matches a predicate.
*/
Assertions hasOperatorErrorsMatching(Predicate, Optional>>>> errorsConsumer);
/**
* Assert that the whole verification took strictly less than the provided
* duration to execute.
* @param d the expected maximum duration of the verification
*/
Assertions tookLessThan(Duration d);
/**
* Assert that the whole verification took strictly more than the provided
* duration to execute.
* @param d the expected minimum duration of the verification
*/
Assertions tookMoreThan(Duration d);
}
/**
* Allow to set expectations about the {@link Context} propagated during the {@link Subscription}
* phase. You then need to resume to the {@link StepVerifier} assertion of the sequence
* by using {@link #then()}.
*
* @param the type of the sequence
*/
interface ContextExpectations {
/**
* Check that the propagated {@link Context} contains a value for the given key.
*
* @param key the key to check
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations hasKey(Object key);
/**
* Check that the propagated {@link Context} is of the given size.
*
* @param size the expected size
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations hasSize(int size);
/**
* Check that the propagated {@link Context} contains the given value associated
* to the given key.
*
* @param key the key to check for
* @param value the expected value for that key
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations contains(Object key, Object value);
/**
* Check that the propagated {@link Context} contains all of the key-value pairs
* of the given {@link Context}.
*
* @param other the other {@link Context} whose key-value pairs are expected to be
* all contained by the propagated Context.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations containsAllOf(Context other);
/**
* Check that the propagated {@link Context} contains all of the key-value pairs
* of the given {@link Map}.
*
* @param other the other {@link Map} whose key-value pairs are expected to be all
* contained by the propagated {@link Context}.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations containsAllOf(Map, ?> other);
/**
* Check that the propagated {@link Context} contains all of the key-value pairs
* of the given {@link Context}, and nothing else.
*
* @param other the {@link Context} representing the exact expected content of the
* propagated Context.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations containsOnly(Context other);
/**
* Check that the propagated {@link Context} contains all of the key-value pairs
* of the given {@link Map}, and nothing else.
*
* @param other the {@link Map} representing the exact expected content of the
* propagated {@link Context}.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations containsOnly(Map, ?> other);
/**
* Apply custom assertions to the propagated {@link Context}.
*
* @param assertingConsumer a {@link Consumer} for the {@link Context} that applies
* custom assertions.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations assertThat(Consumer assertingConsumer);
/**
* Check that the propagated {@link Context} matches a given {@link Predicate}.
*
* @param predicate a {@link Predicate} to test with the {@link Context}.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations matches(Predicate predicate);
/**
* Check that the propagated {@link Context} matches a given {@link Predicate}
* that is given a description.
*
* @param predicate a {@link Predicate} to test with the {@link Context}
* @param description the description for the Predicate, which will be part of
* the {@link AssertionError} if the predicate fails.
* @return this {@link ContextExpectations} for further Context checking
*/
ContextExpectations matches(Predicate predicate, String description);
/**
* Add the context checks set up by this {@link ContextExpectations} as a
* {@link StepVerifier} expectation, and return the current {@link Step} in order
* to build further sequence expectations and ultimately {@link StepVerifier#verify()
* verify} the sequence.
*
* @return the {@link Step} for further building of expectations.
*/
Step then();
}
}