Add quarkus-flow-testing module for testing workflow executions - #494
Add quarkus-flow-testing module for testing workflow executions#494mcruzdev wants to merge 2 commits into
Conversation
|
🚀 PR Preview e041260 has been successfully built and deployed to https://quarkiverse-flow-pr-494-preview.surge.sh |
e55024b to
3215fa0
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new quarkus-flow-testing module intended to help Quarkus Flow users record workflow lifecycle events, wait for async executions, and assert event sequences in tests.
Changes:
- Introduces event recording/storage models, listener/recorder beans, and fluent assertion/wait APIs.
- Adds tests and README documentation demonstrating ordered, unordered, async, and instance-filtered assertions.
- Wires test beans into the core deployment processor during Quarkus test launch mode.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 27 comments.
Show a summary per file
| File | Description |
|---|---|
pom.xml |
Adds the testing module and changes AssertJ dependency management scope. |
testing/pom.xml |
Defines the new testing utility module and dependencies. |
testing/README.md |
Documents intended usage and API examples. |
testing/src/main/resources/META-INF/beans.xml |
Marks the testing module as a bean archive. |
core/deployment/src/main/java/io/quarkiverse/flow/deployment/FlowTestingProcessor.java |
Registers testing beans in TEST mode. |
testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventStore.java |
Adds event storage with thread-local/shared modes. |
testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventRecorder.java |
Adds injectable recorder facade. |
testing/src/main/java/io/quarkiverse/flow/testing/TestWorkflowExecutionListener.java |
Records workflow/task lifecycle callbacks. |
testing/src/main/java/io/quarkiverse/flow/testing/FluentEventAssertions.java |
Adds synchronous fluent event assertions. |
testing/src/main/java/io/quarkiverse/flow/testing/AsyncFluentEventAssertions.java |
Adds polling-based async wait/assert transition API. |
testing/src/main/java/io/quarkiverse/flow/testing/EventWaiter.java |
Adds alternate event waiting helper. |
testing/src/main/java/io/quarkiverse/flow/testing/ConfigurableAssertions.java |
Defines configurable assertion entry-point API. |
testing/src/main/java/io/quarkiverse/flow/testing/DefaultConfigurableAssertions.java |
Implements configurable assertion delegation. |
testing/src/main/java/io/quarkiverse/flow/testing/OrderableFluentEventAssertions.java |
Adds an ordering-oriented assertion interface. |
testing/src/main/java/io/quarkiverse/flow/testing/events/EventType.java |
Defines recorded event types. |
testing/src/main/java/io/quarkiverse/flow/testing/events/RecordedWorkflowEvent.java |
Wraps workflow/task lifecycle events with metadata. |
testing/src/test/java/io/quarkiverse/flow/testing/ComprehensiveTestingFrameworkTest.java |
Demonstrates broad framework usage. |
testing/src/test/java/io/quarkiverse/flow/testing/EventWaiterTest.java |
Tests event waiting behavior. |
testing/src/test/java/io/quarkiverse/flow/testing/FluentEventAssertionsTest.java |
Tests basic fluent assertions. |
testing/src/test/java/io/quarkiverse/flow/testing/InstanceIdFilteringTest.java |
Tests filtering by workflow instance ID. |
testing/src/test/java/io/quarkiverse/flow/testing/IntegratedWaitAndAssertTest.java |
Tests wait-then-assert chaining. |
testing/src/test/java/io/quarkiverse/flow/testing/OrderedAndUnorderedAssertionsTest.java |
Tests ordered and unordered assertion modes. |
testing/src/test/java/io/quarkiverse/flow/testing/SequentialInstanceWaitTest.java |
Tests waiting across multiple instances. |
Comments suppressed due to low confidence (9)
testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventRecorder.java:109
- The recorder's
waitFor()returns the olderEventWaiter, which has nothenAssert()/forInstance()chaining and diverges fromWorkflowEventStore.waitFor()returningAsyncFluentEventAssertions. Since this recorder is the injected entry point, users cannot access the integrated wait-and-assert API through it.
public EventWaiter waitFor() {
return new EventWaiter(eventStore);
testing/src/main/java/io/quarkiverse/flow/testing/FluentEventAssertions.java:470
withError()has the same state issue aswithOutput(): in default unordered mode the preceding event assertion does not advancecurrentIndex, so.workflowFailed().withError(...)fails before inspecting the matched failed event.
public FluentEventAssertions withError(Consumer<Throwable> errorAssertion) {
if (currentIndex == 0) {
throw new AssertionError("No event has been verified yet. Call an event assertion method first.");
}
RecordedWorkflowEvent event = events.get(currentIndex - 1);
testing/src/main/java/io/quarkiverse/flow/testing/AsyncFluentEventAssertions.java:78
Duration.ZEROis accepted here even though the message says the interval must be positive. That results in a zero-millisecond sleep in the polling loop, which can spin aggressively until timeout.
public AsyncFluentEventAssertions pollInterval(Duration pollInterval) {
if (pollInterval == null || pollInterval.isNegative()) {
throw new IllegalArgumentException("Poll interval must be positive");
}
this.pollInterval = pollInterval;
testing/src/main/java/io/quarkiverse/flow/testing/EventWaiter.java:57
- A zero poll interval is accepted here and later passed to
Thread.sleep(0), which can turn the timeout loop into a tight spin. Reject zero values to match the positive-duration contract.
public EventWaiter pollInterval(Duration pollInterval) {
if (pollInterval == null || pollInterval.isNegative()) {
throw new IllegalArgumentException("Poll interval must be positive");
}
this.pollInterval = pollInterval;
testing/src/main/java/io/quarkiverse/flow/testing/EventWaiter.java:248
sequence()is documented as waiting for multiple events, but it only copies configuration onto the supplied waiters and never invokes any wait operation. Calling this method by itself therefore performs no waiting and can give tests false confidence.
public EventWaiter sequence(EventWaiter... waiters) {
for (EventWaiter waiter : waiters) {
waiter.timeout(this.timeout).pollInterval(this.pollInterval);
}
return this;
testing/src/main/java/io/quarkiverse/flow/testing/DefaultConfigurableAssertions.java:31
- Similarly, changing the instance filter after the delegate has been created does not rebuild or re-filter the delegate. A chain that resets and switches instances can keep asserting against the previous unfiltered/filtered event list.
@Override
public ConfigurableAssertions forInstance(String instanceID) {
this.instanceID = Objects.requireNonNull(instanceID, "instanceID must not be null");
return this;
testing/README.md:180
- The API reference documents a public boolean constructor that does not exist. Since
WorkflowEventStore(boolean)is private, users cannot create shared stores this way.
**Creation:**
```java
new WorkflowEventStore() // Thread-local storage (default)
new WorkflowEventStore(true) // Shared storage for async workflows
testing/README.md:265
assertAll()is documented as a required execution step, but neitherFluentEventAssertionsnorConfigurableAssertionsdefines that method; assertions execute eagerly in the current implementation. Examples that include.assertAll()will not compile.
**Execution:**
- `assertAll()` - Execute all assertions (required!)
testing/README.md:205
- The documented
polling()andstreaming()methods are not implemented onAsyncFluentEventAssertions. These examples and API bullets will not compile unless those mode-selection methods are added.
**Configuration:**
- `timeout(Duration)` - Set max wait time (default: 5s)
- `pollInterval(Duration)` - Set poll interval (default: 50ms)
- `polling()` - Use polling mode (default)
- `streaming()` - Use streaming mode (future)
- `forInstance(String)` - Filter events by workflow instance ID
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @BuildStep | ||
| AdditionalBeanBuildItem additionalTestBeans(LaunchModeBuildItem launchMode, CombinedIndexBuildItem index) { | ||
| if (launchMode.getLaunchMode() == LaunchMode.TEST | ||
| && index.getIndex().getClassByName(WORKFLOW_EVENT_STORE) != null) { | ||
| return AdditionalBeanBuildItem.builder() |
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>

This pull request introduces a new testing utilities module for Quarkus Flow workflows, providing a fluent API for asynchronous event waiting and assertions in workflow tests. It adds the
testingmodule to the build, implements a robust event assertion DSL, and integrates the necessary deployment logic to register testing beans during test runs.The most important changes are:
Testing Utilities Module Addition
testingmodule (quarkus-flow-testing) to the project, including its ownpom.xmlwith dependencies for workflow testing and assertion libraries. [1] [2]Fluent Event Assertion API
AsyncFluentEventAssertions, a fluent API for waiting on workflow events with configurable timeouts and polling, enabling expressive and readable test code for workflow event assertions.ConfigurableAssertionsinterface and its default implementation, providing a configurable assertion interface for workflow events, supporting strict ordering, instance filtering, and chaining of assertions. [1] [2]Deployment Integration
FlowTestingProcessorto register test beans automatically during test launches, ensuring test utilities are available in the Quarkus test context.Dependency Scope Adjustment
assertj-corein the mainpom.xmlfromtestto compile, ensuring assertion utilities are available to the new testing module.Closes #469