Workflow Executor
Author:
Kirill Gaiduk
Changed on:
5 Sept 2025
Overview
While the `RuleExecutor`
is perfect for unit testing a single Rule, the `WorkflowExecutor`
is the tool of choice for integration testing an entire Workflow or parts of it. It reads a Workflow definition from a JSON file and simulates the Fluent Orchestration Engine by executing the sequence of Rules and Rulesets.
Key points
- Integration Testing Focus:
`WorkflowExecutor`
is the primary tool for integration testing an entire or parts of a Workflow. - Workflow Simulation: It mimics the Fluent Commerce Orchestration Engine by processing an initial Event and then passing the resulting Event to the next Ruleset in the chain.
- Rule Mocking: The
`.mockRule()`
method is a powerful feature that allows you to replace a real Rule in the Workflow with a mock implementation. This is crucial for isolating the part of the Workflow you want to test. - Targeted API Mocking: The
`.mockNamedQuery()`
method lets you mock a GraphQL query only when it is called by a specific Rule, giving you fine-grained control over test data for different stages of the Workflow. - Consolidated Assertions: The
`execute`
method returns a single Context containing a consolidated list of all Actions from all Rules, allowing you to assert the final state.
Core Methods
The `WorkflowExecutor`
builds upon the capabilities of the previous `TestExecutor`
and adds several new features:
`of`
This method automatically loads Workflows from a JSON file and includes Rules within the project.
1WorkflowExecutor workflowExecutor = WorkflowExecutor.of("workflows/basic-order.json");
`mockRule`
The `WorkflowExecutor`
allows you to simulate Rule executions by specifying mock behaviors for Rules based on their names, bypassing the need for actual Rule execution.
In other words, you can instruct the `WorkflowExecutor`
: “Whenever a Rule with this name is encountered, skip the actual execution and simply assume it produced this Action.”
1workflowExecutor.mockRule("SendEvent", c -> c.v2().action().sendEvent(
2 Event.builder().name((String) c.getRule().getProps().get("eventName")).build()
3));
`mockNamedQuery`
In order to define specific mock API responses for each Rule identified by name you can use this method.
1workflowExecutor.mockNamedQuery("ValidateOrder",
2 GetOrderWithItemsAndFulfilmentsQuery.class,
3 "graphql/GetOrderWithItemsAndFulfilments/one-item_none-fulfilled.json")
After running the `WorkflowExecutor`
, it returns a `RuleContextGenerator`
that you can use to assert the expected outcomes of the Workflow execution.
1RuleContextGenerator context = WorkflowExecutor.of("workflows/basic-order.json")
2 .mockRule(
3 "SendEvent", c -> c.context().action().sendEvent(
4 Event.builder().name((String) c.getRule().getProps().get("eventName")).build()))
5 .mockNamedQuery("ValidateOrder",
6 GetOrderWithItemsAndFulfilmentsQuery.class,
7 "graphql/GetOrderResponse.json")
8 .execute(
9 Event.builder().name("CREATE").build());
10assertEquals(2, context.getActionsOfType(TestActions.SendEventAction.class).size());
11assertEquals("ValidateOrder", context.getLastActionOfType(TestActions.SendEventAction.class).getEvent().getName());
`ignoreRules`
In order to ignore a specific set of rules during test execution, use the `WorkflowExecutor.ignoreRules`
method.
1WorkflowExecutor.ignoreRules(
2 "TEST.mock.LogEvent",
3 "TEST.mock.SendWebhook"
4)
`execute`
Use the `WorkflowExecutor.execute`
method to execute the workflow and return the `TestContext`
with any produced actions. This method runs the workflow with a default event that has no attributes and references an order entity.
1TestContext testContext = WorkflowExecutor.execute()
`execute`
(event)
Use the `WorkflowExecutor.execute`
method with an event parameter to execute the workflow with a specific Event and return the `TestContext`
with any actions that were produced. This version allows you to run the rule with custom event attributes and/or entity details.
1Event createEvent = Event.builder()
2 .name("CREATE")
3 .entityRef("order-123")
4 .build();
5TestContext testContext = RuleContextGenerator context = executor.execute(createEvent);
Complete Example
Testing with `WorkflowExecutor`
is similar to the `RuleExecutor`
but is designed to test the interactions between rules.
1. Test Workflow
First, you need a workflow definition file in your `src/test/resources`
directory (e.g., `workflows/my_order_workflow.json`
). This file defines the sequence of rules that are triggered by specific events.
1{
2 "name": "CREATE",
3 "rules": [
4 {
5 "name": "TEST.custom.ValidateOrder",
6 "props": null
7 },
8 {
9 "name": "TEST.custom.SendOrderWebhookEvent",
10 "props": {
11 "eventName": "SendOrderCreateWebhook"
12 }
13 },
14 {
15 "name": "TEST.core.SendEvent",
16 "props": {
17 "eventName": "RunSourcingLogic"
18 }
19 }
20 ]
21}
2. Test Class
The test class uses the `WorkflowExecutor`
to load the workflow, mock any necessary dependencies, and execute it with an initial event.
1import com.fluentcommerce.util.test.executor.WorkflowExecutor;
2import com.fluentcommerce.util.test.executor.RuleContextGenerator;
3import com.fluentretail.rubix.event.Event;
4import org.junit.jupiter.api.Test;
5
6public class MyOrderWorkflowTest {
7
8 @Test
9 void test_FullOrderWorkflow() {
10 // 1. Arrange: Load the workflow file
11 WorkflowExecutor executor = WorkflowExecutor.of("workflows/my_order_workflow.json")
12 // Mock a GraphQL query for a specific rule in the workflow
13 .mockNamedQuery(
14 "TEST.custom.ValidateOrder",
15 GetOrderByIdQuery.class,
16 "data/order_to_validate.json"
17 )
18 // Mock a rule entirely. When SendOrderWebhookEvent is called,
19 // just log a message instead of running the real rule.
20 .mockRule(
21 "TEST.custom.SendOrderWebhookEvent",
22 context -> context.context().log("SendOrderWebhookEvent rule was called")
23 );
24
25 // 2. Act: Execute the workflow with an initial event
26 Event initialEvent = Event.builder()
27 .name("CREATE")
28 .entityRef("order-123")
29 .build();
30 RuleContextGenerator context = executor.execute(initialEvent);
31
32 // 3. Assert: Verify the final state
33 // Check the sequence of events that were created
34 assertEquals(1, context.getActionsOfType(SendEventAction.class).size());
35 assertEquals("RunSourcingLogic", context.getLastActionOfType(SendEventAction.class).getEvent().getName());
36
37 // Check that the mocked rule's log message exists
38 assertEquals(1, context.getActionsOfType(LogAction.class).size());
39 assertTrue(context.getLastActionOfType(LogAction.class).getMessage().contains("SendOrderWebhookEvent rule was called"));
40 }
41}