From reactor
Test Reactor publishers with reactor-test using StepVerifier, virtual time, TestPublisher, and PublisherProbe. Use this skill when designing or reviewing Reactor tests with reactor-test: StepVerifier workflow, virtual time, request and cancellation assertions, TestPublisher, PublisherProbe, and ordinary post-verification checks.
npx claudepluginhub ririnto/sinon --plugin reactorThis skill uses the workspace's default tool permissions.
Test Reactor publishers with the ordinary `reactor-test` path.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Share bugs, ideas, or general feedback.
Test Reactor publishers with the ordinary reactor-test path.
This skill covers everyday StepVerifier flow, success/empty/error assertions, virtual time, request and cancellation assertions, TestPublisher, PublisherProbe, and ordinary post-verification checks. Keep advanced StepVerifierOptions, context-specific expectations, timeout-heavy scenarios, and noncompliant publishers in blocker references.
Flux or Mono with StepVerifierTestPublisherPublisherProbeFlux / Mono operator design as the main problemWebTestClient| reactor-test surface | Keep in this file | Open a reference when... |
|---|---|---|
ordinary StepVerifier workflow | create, expect signals, verify | scenario options or complex choreography become the blocker |
| success, empty, and error assertions | expectNext, expectComplete, expectError* | custom predicates or richer assertion flows dominate the test |
| virtual time | withVirtualTime(...), expectNoEvent(...), thenAwait(...) | scheduler caveats or complex timing behavior become the blocker |
| request and cancellation | initial request, thenRequest(...), thenCancel() | the test is mainly about request choreography |
TestPublisher | manual emission for downstream testing | spec-violation or noncompliant publisher behavior becomes the blocker |
PublisherProbe | verify subscription path without real data exchange | probe wrapping and advanced branch checks dominate the test |
| post-execution assertions | verifyThenAssertThat() for dropped/discarded checks | hook-heavy or context-specific assertions become the blocker |
| context test boundary | recognize context assertions as part of advanced verification | context expectations become the main job |
StepVerifier.create(...) unless time control is required.withVirtualTime(...) for delayed or interval-based publishers.TestPublisher when the test must control upstream signals directly.PublisherProbe when the test is really about which branch was subscribed.verifyThenAssertThat() when dropped or discarded signals matter after execution.StepVerifier.TestPublisher.PublisherProbe.StepVerifier.create(...).StepVerifier.withVirtualTime(...).expectNext(...), expectNextCount(...), assertNext(...).expectComplete().expectError*.thenRequest(...), thenCancel().| Need | Default move | Why |
|---|---|---|
| basic publisher verification | StepVerifier.create(publisher) | ordinary signal assertions |
| empty completion | expectComplete() | asserts no further signals |
| specific error type | expectError(Class) | keeps error intent explicit |
| complex value assertion | assertNext(consumer) | multi-property inspection per value |
| time-based operator | StepVerifier.withVirtualTime(() -> publisher) | avoids real delays |
| no events during a window | expectSubscription().expectNoEvent(duration) | subscription itself is an event |
| additional demand mid-test | thenRequest(n) | controls request flow |
| manual upstream source | TestPublisher.create() | emits signals on demand |
| branch selection check | PublisherProbe.empty() or PublisherProbe.of(...) | verifies subscription path |
| dropped/discarded checks | verifyThenAssertThat() | post-execution assertions |
| recorded-signal inspection | verifyThenAssertThat().consumeRecordedWith(...) | inspect all recorded signals after verification |
StepVerifier success pathimport org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
class BasicStepVerifierTest {
@Test
void verifiesValuesAndCompletion() {
Flux<String> flux = Flux.just("alpha", "beta", "gamma");
StepVerifier.create(flux)
.expectNext("alpha", "beta", "gamma")
.verifyComplete();
}
}
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
class VirtualTimeTest {
@Test
void verifiesDelayedSignalWithoutWaiting() {
StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofSeconds(5)))
.expectSubscription()
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(0L)
.verifyComplete();
}
}
TestPublisher for manual upstream controlimport org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
import reactor.test.publisher.TestPublisher;
class TestPublisherExample {
@Test
void emitsOnDemand() {
TestPublisher<String> publisher = TestPublisher.create();
StepVerifier.create(publisher.flux())
.then(() -> publisher.emit("first", "second"))
.expectNext("first", "second")
.verifyComplete();
}
}
PublisherProbe for fallback path verificationimport org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.publisher.PublisherProbe;
class PublisherProbeExample {
@Test
void verifiesFallbackSubscription() {
PublisherProbe<String> fallback = PublisherProbe.empty();
Mono<String> result = Mono.<String>empty().switchIfEmpty(fallback.mono());
StepVerifier.create(result)
.verifyComplete();
fallback.assertWasSubscribed();
}
}
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
class RequestAndCancellationExample {
@Test
void controlsDemandExplicitly() {
StepVerifier.create(Flux.range(1, 10), 1)
.expectNext(1)
.thenRequest(2)
.expectNext(2, 3)
.thenCancel()
.verify();
}
}
import org.junit.jupiter.api.Test;
import java.time.Duration;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
class PostVerificationExample {
@Test
void checksExecutionTime() {
StepVerifier.create(Flux.just("a", "b"))
.expectNext("a", "b")
.verifyThenAssertThat()
.tookLessThan(Duration.ofSeconds(1));
}
}
assertNext(...) for complex value assertionsUse assertNext(...) when a value needs multi-assertion inspection beyond simple equality.
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
class AssertNextExample {
@Test
void assertsMultiplePropertiesPerValue() {
StepVerifier.create(Flux.just("alpha", "beta", "gamma"))
.expectNext("alpha")
.assertNext(value -> {
assertThat(value).hasSizeGreaterThan(1);
assertThat(value).startsWith("b");
})
.expectNext("gamma")
.verifyComplete();
}
}
assertNext(...) receives the value and allows arbitrary assertions inside the lambda. Use it when expectNext(...) equality matching is not expressive enough.
delayElement caveatdelayElement uses Schedulers.parallel() by default, which bypasses virtual time just like subscribeOn. Use delaySubscription instead when working inside withVirtualTime, or ensure no real-scheduler call appears in the publisher chain.
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
class DelayElementVirtualTimeTest {
@Test
void badDelayElementInVirtualTime() {
StepVerifier.withVirtualTime(() ->
Mono.just("value").delayElement(Duration.ofSeconds(5))
)
.expectSubscription()
.expectNoEvent(Duration.ofSeconds(5))
.expectNext("value")
.verifyComplete();
}
@Test
void goodVirtualTimeWithoutDelayElement() {
StepVerifier.withVirtualTime(() ->
Mono.just("value").delaySubscription(Duration.ofSeconds(5))
)
.expectSubscription()
.expectNoEvent(Duration.ofSeconds(5))
.expectNext("value")
.verifyComplete();
}
}
The first test may hang or fail because delayElement uses the real parallel() scheduler. The second test uses delaySubscription which is compatible with virtual time.
| Anti-pattern | Why it fails | Correct move |
|---|---|---|
forgetting to call verify() or a shortcut | the scenario never executes | end every verifier chain with verify* |
| building the virtual-time publisher outside the supplier | timers are created too early | create the publisher inside withVirtualTime(...) |
| using real time for delayed publishers | tests become slow or flaky | use virtual time |
using StepVerifier when the real question is branch selection | signal assertions miss subscription-path intent | use PublisherProbe |
| asserting complicated upstream timing without control | behavior stays nondeterministic | use TestPublisher |
StepVerifier, virtual time, TestPublisher, and PublisherProbe.| Open this when... | Reference |
|---|---|
| basic verifier flow is not enough and you need scenario options, context expectations, or richer post-verification assertions | Advanced StepVerifier |
| the blocker is timeout behavior or virtual-time failure cases | Testing Errors and Edge Cases |
the blocker is a noncompliant TestPublisher or deliberate spec-edge behavior | Noncompliant TestPublishers |
Return: