Rule Executor
Author:
Kirill Gaiduk
Changed on:
5 Sept 2025
Overview
The `RuleExecutor`
is the primary tool in the `util-test`
library for unit testing a single rule in isolation. It provides a builder-style interface for setting up a mock `Context`
, executing your rule's `run`
method, and asserting the results.
Key points
- Unit Testing Focus:
`RuleExecutor`
is the main tool for testing a single rule in isolation. - Arrange-Act-Assert: It's designed to follow the clean Arrange-Act-Assert pattern. You use the constructor to arrange the context, call
`execute()`
to act, and use`getActions()`
to assert the outcome. - Rule Properties Configuration: Rule properties are passed as a
`Map`
in the`RuleExecutor.of(Class, Map)`
constructor. - Event Configuration: Custom events can be passed to the
`execute(Event)`
method. - GraphQL Mocking: The
`.mockNamedQuery()`
method is a critical feature that allows you to provide predefined JSON responses for any GraphQL query your rule executes, enabling you to test different data scenarios without making live API calls.
Arrange-Act-Assert
Testing with `RuleExecutor`
follows the standard Arrange-Act-Assert pattern, which makes tests clean and easy to understand by clearly separating each phase:
Arrange (Build the Context)
This is the setup phase where you define all the conditions for your test case. You start by calling `RuleExecutor.of(MyRule.class, Map)`
with rule properties.
1// ARRANGE: Set up the rule executor with properties and a mock query
2RuleExecutor executor = RuleExecutor.of(MyRuleToTest.class, ImmutableMap.of(
3 "eventName", "success-event"
4))
5.mockNamedQuery(
6 GetOrderQuery.class,
7 "graphql/getOrderResult.json"
8);
9
10Event event = Event.builder()
11 .name("TestEvent")
12 .entityRef("order-123")
13 .entityType("ORDER")
14 .attributes(ImmutableMap.of(
15 "eventAttributeKey", "eventAttributeValue"
16 ))
17.build();
Act (Execute the Rule)
This is the simplest step. The `execute()`
method runs your rule's `run`
method using the mock context you just configured.You can pass a custom event if needed. You can pass a custom event if needed.
1// ACT
2RuleContextGenerator context = executor.execute();
3
Assert (Verify the Outcome)
After execution, you can retrieve the actions that your rule performed and use the built-in fluent assertions to verify them.
1// ASSERT
2// Verify exactly one event was sent
3assertEquals(context.getActionsOfType(TestActions.SendEventAction.class).size(), 1);
4
5// Verify the event name
6assertEquals(
7 context.getLastActionOfType(TestActions.SendEventAction.class).getEvent().getName(),
8 "success-event"
9);
10
11// Verify no other actions were performed
12assertEquals(context.getActionsOfType(TestActions.MutateAction.class).size(), 0);
13assertEquals(context.getActionsOfType(TestActions.WebhookAction.class).size(), 0);
Core Methods
`of`
(ruleName, props)
To create a `RuleExecutor`
for a rule specified by its name, use the `RuleExecutor.of`
method passing the specific name of the Rule.
1RuleExecutor executor = RuleExecutor.of("TestIfAttributeEquals",
2 ImmutableMap.of(
3 "event_name", "attribute-matches",
4 "attributeName", "aaa",
5 "attributeValue", "bbb"
6)).execute();
`of`
(className, props)
To create a RuleExecutor for a rule specified by its class, use the RuleExecutor.of method passing the specific rule class.
1RuleContextGenerator context = RuleExecutor.of(
2 TestUpdateOrder.class,
3 ImmutableMap.of()
4)
5.execute();
`execute`
(event)
If you need to execute a Rule with an Event containing attributes or entity details, use the `RuleExecutor`
to pass in the customized Event. Any null fields will be auto-populated with values referencing an Order entity.
1RuleContextGenerator context = RuleExecutor.of(IfPropertyEquals.class,
2 ImmutableMap.of(
3 "jsonpath", "event.attributes.myEventAttribute",
4 "value", "YES",
5 "eventName", "success"
6 )
7)
8.execute(Event.builder().attributes(ImmutableMap.of("myEventAttribute", "YES")).build());
`resetApi`
Use the `RuleExecutor.resetApi`
method to create a new API mock and clear any previously mocked queries. This allows you to start each test with a clean slate and ensures that previous mocks do not interfere with the current test execution.
1RuleExecutor ruleExecutor = executor.resetApi()
`replaceApi`
To replace the executor’s API mock with your own MockApiClient, use the `RuleExecutor.replaceApi`
method. This allows you to reuse the same mock configuration across multiple tests, ensuring consistency and reducing setup overhead.
1final MockApiClient api = new MockApiClient();
2RuleExecutor ruleExecutor = executor.replaceApi(api)
`withLegacyClient`
To use the provided legacy client instead of a stubbed one, utilize the `RuleExecutor.withLegacyClient`
method. This enables you to connect to a real client for more accurate testing or simulate a specific client setup during the execution.
1@Mock
2private ReadOnlyFluentApiClient apiClient;
3.....
4RuleExecutor RuleExecutor = executor.withLegacyClient(apiClient)
Complete Example
1import com.fluentcommerce.util.test.executor.RuleExecutor;
2import com.fluentcommerce.util.test.executor.RuleContextGenerator;
3import com.fluentretail.rubix.event.Event;
4import org.junit.jupiter.api.Test;
5
6import static com.fluentcommerce.util.test.executor.TestActions.SendEventAction;
7import static org.junit.jupiter.api.Assertions.*;
8
9public class MyRuleTest {
10
11 @Test
12 void testMyRule() {
13 // ARRANGE: Set up the rule executor with properties
14 RuleExecutor executor = RuleExecutor.of(MyRule.class, ImmutableMap.of(
15 "eventName", "success-event",
16 "threshold", "100"
17 ))
18 .mockNamedQuery(
19 GetOrderQuery.class,
20 "graphql/getOrderResult.json"
21 );
22
23 // Create custom event
24 Event customEvent = Event.builder()
25 .name("OrderCreated")
26 .entityRef("order-123")
27 .entityType("ORDER")
28 .attributes(ImmutableMap.of(
29 "orderValue", "150.00",
30 "customerType", "PREMIUM"
31 ))
32 .build();
33
34 // ACT: Execute the rule with custom event
35 RuleContextGenerator context = executor.execute(customEvent);
36
37 // ASSERT: Verify the results
38 assertEquals(1, context.getActionsOfType(SendEventAction.class).size());
39 assertEquals("success-event", context.getLastActionOfType(SendEventAction.class).getEvent().getName());
40
41 // Verify no other actions were performed
42 assertEquals(0, context.getActionsOfType(MutateAction.class).size());
43 assertEquals(0, context.getActionsOfType(WebhookAction.class).size());
44 }
45}