Fluent Commerce Logo
Docs
Sign In
Essential knowledge

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}