Skip to content

Add quarkus-flow-testing module for testing workflow executions - #494

Draft
mcruzdev wants to merge 2 commits into
quarkiverse:mainfrom
mcruzdev:issue-469
Draft

Add quarkus-flow-testing module for testing workflow executions#494
mcruzdev wants to merge 2 commits into
quarkiverse:mainfrom
mcruzdev:issue-469

Conversation

@mcruzdev

@mcruzdev mcruzdev commented Apr 28, 2026

Copy link
Copy Markdown
Member

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 testing module 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

  • Added a new testing module (quarkus-flow-testing) to the project, including its own pom.xml with dependencies for workflow testing and assertion libraries. [1] [2]

Fluent Event Assertion API

  • Implemented AsyncFluentEventAssertions, a fluent API for waiting on workflow events with configurable timeouts and polling, enabling expressive and readable test code for workflow event assertions.
  • Introduced ConfigurableAssertions interface 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

  • Added FlowTestingProcessor to register test beans automatically during test launches, ensuring test utilities are available in the Quarkus test context.

Dependency Scope Adjustment

  • Changed the scope of assertj-core in the main pom.xml from test to compile, ensuring assertion utilities are available to the new testing module.

Closes #469

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown

🚀 PR Preview e041260 has been successfully built and deployed to https://quarkiverse-flow-pr-494-preview.surge.sh

@mcruzdev
mcruzdev force-pushed the issue-469 branch 2 times, most recently from e55024b to 3215fa0 Compare May 18, 2026 18:43
@mcruzdev
mcruzdev requested a review from Copilot May 19, 2026 03:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 older EventWaiter, which has no thenAssert()/forInstance() chaining and diverges from WorkflowEventStore.waitFor() returning AsyncFluentEventAssertions. 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 as withOutput(): in default unordered mode the preceding event assertion does not advance currentIndex, 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.ZERO is 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 neither FluentEventAssertions nor ConfigurableAssertions defines 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() and streaming() methods are not implemented on AsyncFluentEventAssertions. 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.

Comment thread pom.xml
Comment thread testing/pom.xml
Comment thread testing/pom.xml Outdated
Comment thread testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventStore.java Outdated
Comment on lines +19 to +23
@BuildStep
AdditionalBeanBuildItem additionalTestBeans(LaunchModeBuildItem launchMode, CombinedIndexBuildItem index) {
if (launchMode.getLaunchMode() == LaunchMode.TEST
&& index.getIndex().getClassByName(WORKFLOW_EVENT_STORE) != null) {
return AdditionalBeanBuildItem.builder()
Comment thread testing/src/main/java/io/quarkiverse/flow/testing/AsyncFluentEventAssertions.java Outdated
Comment thread testing/src/main/java/io/quarkiverse/flow/testing/EventWaiter.java Outdated
Comment thread testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventStore.java Outdated
Comment thread testing/src/main/java/io/quarkiverse/flow/testing/WorkflowEventStore.java Outdated
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
@mcruzdev mcruzdev added the ⚠️ DO NOT MERGE DO NOT MERGE THIS PR! label Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ DO NOT MERGE DO NOT MERGE THIS PR!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Test Framework for Workflow Event Assertions

2 participants