Skip to main content Link Menu Expand (external link) Document Search Copy Copied
Dark theme

Testing the Context


If you need to test an scenario that involves storing values in the context, StepVerifier.Step provides two methods related to the propagation of a sequence’s context:

// To expect that after the Subscription step, 
// a Context has been propagated.
StepVerifier.ContextExpectations<T> expectAccessibleContext()

// To expect that NO Context was propagated 
// after the Subscription phase.
StepVerifier.Step<T> expectNoAccessibleContext()

These methods are usually placed right after create, but pay attention to their return type.

The method expectNoAccessibleContext() tests that no context is propagated and returns a StepVerifier.Step instance so you can add expectations about the values emitted by the sequence.

On the other hand, expectAccessibleContext() tests that a context is propagated and returns an instance of StepVerifier.ContextExpectations. This interface allows you to check the content of the context.

For example, to apply custom assertions to the propagated Context:

StepVerifier.ContextExpectations<T> assertThat(
    Consumer<Context> assertingConsumer
)

To check that the propagated Context contains the given value associated to the given key:

StepVerifier.ContextExpectations<T> contains(
    Object key, Object value
)

To check that the propagated Context contains all of the key-value pairs of the given Map:

StepVerifier.ContextExpectations<T> containsAllOf(
    Map<?,?> other
)

To check that the propagated Context contains all of the key-value pairs of the given Map, and nothing else:

StepVerifier.ContextExpectations<T> containsOnly(
    Map<?,?> other
)

To check that the propagated Context contains a value for the given key:

StepVerifier.ContextExpectations<T> hasKey(
    Object key
)

To check that the propagated Context is of the given size:

StepVerifier.ContextExpectations<T> hasSize(
    int size
)

Or to check that the propagated Context matches a given Predicate:

StepVerifier.ContextExpectations<T> matches(
    Predicate<Context> predicate
)

All these methods return an instance of StepVerifier.ContextExpectations, so you can keep testing the content of the context.

Once you’re done, you can use the method then() to return a StepVerifier.Step instance that groups all the context checks you set up and tests expectations about the values emitted by the sequence going forward:

StepVerifier.Step<T> then()

Let’s review some examples.

Consider the following sequence:

private final String KEY = "myKey";

Flux<Integer> getFlux() {
    return Flux.just(1, 2, 3, 4)
        .transformDeferredContextual(
                (flux, ctx) ->
                        flux.map(
                            i -> i * ctx.getOrDefault(KEY, 1)
                        )
        );
}

It uses a value stored in the context to multiply its elements.

If we call the method expectNoAccessibleContext() when testing this sequence:

StepVerifier
    .create(getFlux())
    .expectNoAccessibleContext()
    .expectNextCount(4)
    .verifyComplete();

We’ll get a failing test because the method transformDeferredContextual() expects a Context object.

If you change the method to expectAccessibleContext():

StepVerifier
    .create(getFlux())
    .expectAccessibleContext()
    .then()
    .expectNextCount(4)
    .verifyComplete();

Your test should pass.

However, notice that you’ll also need to add the method then() to be able to call expectNextCount(4).

Now, a Context object is created at subscription time, so you might be thinking, is a Context object created here?

Yes.

Remember, the verify method subscribes to the sequence and in this case, a default (empty) Context is created.

We can prove this by printing the Context object with the assertThat method, which receives a Consumer of type Context:

StepVerifier
    .create(getFlux())
    .expectAccessibleContext()
    .assertThat(System.out::println)
    .then()
    .expectNextCount(4)
    .verifyComplete();

This is what gets printed:

Context0{}

This way, if we test, for example, if the Context contains an entry with our key:

StepVerifier
    .create(getFlux())
    .expectAccessibleContext()
    .hasKey(KEY)
    .then()
    .expectNextCount(4)
    .verifyComplete();

The test will fail because the propagated Context is empty.

So how do we add entries to this Context object?

Well, the create method has a version that takes a StepVerifierOptions object:

static <T> StepVerifier.FirstStep<T> create(
    Publisher<? extends T> publisher, 
    StepVerifierOptions options
)

StepVerifierOptions has two methods related to the Context, one to get the initial Context and another one to set it:

// Returns the Context to be propagated 
// initially by the StepVerifier.
Context getInitialContext() 

// Set an initial Context to be propagated by 
// the StepVerifier when it subscribes to the sequence,
// returning the instance of StepVerifierOptions 
// to continue setting more options.
StepVerifierOptions withInitialContext(
    Context context
)

Here’s an example that shows how to use it:

StepVerifier
    .create(getFlux(),
        StepVerifierOptions
                .create()
                .withInitialContext(
                    Context.of(KEY, 10)
                )
    )
    .expectAccessibleContext()
    .hasKey(KEY)
    .then()
    .expectNextCount(4)
    .verifyComplete();

However, if we want to make sure everything is working properly, we can use the expectNext method to pass the exact value we’re expecting:

StepVerifier
    .create(getFlux(),
        StepVerifierOptions
                .create()
                .withInitialContext(
                    Context.of(KEY, 10)
                )
    )
    .expectAccessibleContext()
    .hasKey(KEY)
    .then()
    .expectNext(10, 20, 30, 40)
    .verifyComplete();

In any case, the test(s) should pass.