Reading my Reactor notes, I keep returning to a distinction that ordinary method-call intuition can hide: constructing a pipeline and consuming its signals are separate actions.
A method returning a Flux is not necessarily returning a finished collection. It can return a description of how values will be produced and transformed when something subscribes.
Cardinality is the first clue
Mono<T> represents a sequence with zero or one value. Flux<T> can represent zero to many values. Both can complete or fail. A Mono is therefore not a promise that a value must exist.
This changes what I want to test. I care about the values, their order where the contract requires it, and the terminal signal. An empty completion can be valid behavior; an unexpected error is a different outcome.
The Reactor Mono reference makes that zero-or-one boundary explicit.
Assembly describes the work
Here is a small illustrative pipeline:
Flux<String> labels = Flux.just("cache", "queue")
.map(String::toUpperCase);
The transformation is assembled, but nothing in this snippet consumes the values. In this simple sequence, subscribing drives the values through the transformation.
It is tempting to turn that into an absolute rule that all work is delayed until subscription. That would be too broad. Arguments passed to just have already been evaluated. Hot sources can also produce events independently of a particular subscriber. Reactor’s hot and cold publisher guide explains why the distinction matters.
The useful question is not merely “Where is subscribe?” It is also “When is the underlying work created, and what kind of source am I using?”
Verification is what starts this test
My source notes introduce StepVerifier as a way to describe expected signals. There is one detail worth correcting: StepVerifier.create(...) builds the verifier. Calling verify() or a shortcut such as verifyComplete() triggers subscription and verification.
StepVerifier.create(labels)
.expectNext("CACHE", "QUEUE")
.verifyComplete();
That final line is doing real work. It asks the verifier to subscribe, compare the signals with the expectations, and check successful completion. This behavior is documented in Reactor’s testing guide.
These snippets illustrate the contract; I have not executed them as part of preparing this blog article. A project would need compatible Reactor and test dependencies.
The return type is not a performance guarantee
Returning Mono does not automatically make every operation inside it non-blocking. I still need to understand what each function does and where it executes.
For me, the more useful learning milestone is being able to narrate the lifecycle: assemble a sequence, subscribe to it, observe its values or failure, and verify the promised behavior.
That is a smaller claim than “I understand reactive programming,” but it gives me something concrete to inspect the next time a pipeline behaves unexpectedly.
Adapted with AI assistance from my Project Reactor learning notes, with the subscription timing checked against the official reference. This is a learning explanation, not a production performance report.