Rules SDK Testing Rules
Author:
Fluent Commerce
Changed on:
30 June 2024
Overview
This page describes the best practice testing approaches for rules and plugins.
Key points
- As per standard industry practices, rules should be tested as part of the development cycle, on the developer's local machine.
- Testing on Sandbox after local testing has been completed.
Testing Locally
As per standard industry practices, rules should be tested as part of the development cycle, on the developer's local machine.
Rules should be atomic in size and complexity, they are a great candidate for adopting a TDD approach, whereby you write the test first before you even have one line of rule code. You may have heard the phrase "Red, Green, Refactor", and this is a great approach for writing quality rules, that have high test coverage and quality.
There are 2 ways to test locally:
- Mocked Unit Testing
- Testing Rule Execution within the Test Executor (this is like a mini Rubix simulator that runs on your local machine)
You should always include both types of testing for your rules.
General Best Practices to Writing Tests
- Tests must be isolated and repeatable
- Tests must cover positive and negative paths
- Tests must be proven to fail when they should (Red, Green, Refactor)
- Tests should have a single assertion
- Tests should not assume anything
- Tests should where possible only use mocked dependencies, including data objects, utils, services, etc.
Unit Testing
The first type of tests that should be written is Mocked Unit Tests. These tests are ideal for TDD.
Fully Mocked Unit Tests for `MyCustomRule`
`MyCustomRule`
Below is an example Test Suite for a Rule.
1public class MyCustomRuleTest {
2
3 MyCustomRule rule = new MyCustomRule();
4
5 @Mock
6 Context context;
7
8 @Mock
9 Entity entity;
10
11 @Mock
12 ReadOnlyFluentApiClient apiClient;
13
14 @Mock
15 GetOrderByIdQuery.Data data;
16
17 @Mock
18 GetOrderByIdQuery.OrderById orderById;
19
20 @Mock
21 ActionFactory actionFactory;
22
23 @Before
24 public void setup() {
25
26 MockitoAnnotations.initMocks(this);
27
28 when(context.getProp(anyString())).thenReturn("1000");
29 when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD, Integer.class)).thenReturn(1000);
30 when(context.getEntity()).thenReturn(entity);
31 when(entity.getId()).thenReturn("123");
32 when(context.api()).thenReturn(apiClient);
33 when(apiClient.query(any())).thenReturn(data);
34 when(data.orderById()).thenReturn(orderById);
35 }
36
37 @Test(expected = MissingRequiredParamException.class)
38 public void MyCustomRule_withMissingThresholdParam_ThrowsMissingRequiredParamException() {
39
40 //arrange:
41 when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD)).thenReturn(null);
42
43 //act:
44 rule.run(context);
45
46 //assert:
47
48 }
49
50 @Test(expected = MissingRequiredParamException.class)
51 public void MyCustomRule_withEmptyThresholdParam_ThrowsMissingRequiredParamException() {
52
53 //arrange:
54 when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD)).thenReturn("");
55
56 //act:
57 rule.run(context);
58
59 //assert:
60
61 }
62
63 @Test
64 public void MyCustomRule_withValueGreaterThanThreshold_AddsAttributeHIGH_VALUE() {
65
66 //arrange:
67 when(orderById.totalPrice()).thenReturn(1001.00);
68 when(context.action()).thenReturn(actionFactory);
69
70 //act:
71 rule.run(context);
72
73 //assert:
74 verify(actionFactory, times(1)).mutation(any());
75
76 }
77
78 @Test
79 public void MyCustomRule_withValueLessThanThreshold_DoesNothing() {
80
81 //arrange:
82 when(orderById.totalPrice()).thenReturn(999.00);
83
84 //act:
85 rule.run(context);
86
87 //assert:
88 verify(actionFactory, never()).mutation(any());
89
90 }
91
92 @Test
93 public void MyCustomRule_withValueEqualThanThreshold_DoesNothing() {
94
95 //arrange:
96 when(orderById.totalPrice()).thenReturn(1000.00);
97
98 //act:
99 rule.run(context);
100
101 //assert:
102 verify(actionFactory, never()).mutation(any());
103
104 }
105}
Language: java
Name: Example Test Suite for a Rule
Description:
[Warning: empty required content area]The TestExecutor
The
`TestExecutor`
It provides the following methods:
- : load a rule
`rule(Class.class)`
- : adds a ruleset to the workflow
`ruleset(RuleSet ruleSet)`
- : adds an entity to the workflow
`entity(Entity entity)`
- : validates the workflow with the given event
`validateWorkflow(Event event)`
- : executes the workflow with the given event
`execute(Event event)`
The `TestContext`
`TestContext`
The
`TestContext`
- : Provides the action interfaces of the fluent retail API client.
`action()`
- : Provides the API interfaces of the fluent retail API client.
`api()`
- : Provides access to the number of events or actions that have been executed in the context.
`count*()`
- : Provides access to the entities, rules, events, and properties.
`get*()`
The `scanAndValidateAllRules()`
Method
`scanAndValidateAllRules()`
The
`scanAndValidateAllRules()`
Testing on Sandbox
Once you've got good coverage of your Rules in Unit Tests and using the TestExecutor locally, you can deploy your plugin to your Sandbox account.
Once uploaded, you will want to test your rule in a workflow. In many cases, it may be best to test your rule in an isolated ruleset first, where you can trigger it directly with a specific event, and assess the results via the Audit Events. This should include positive and negative test scenarios. For example, if you rule is expected to fail under certain conditions, make sure to test those conditions, and that the resulting audit log reflects the expected information to help users and support staff understand what happened and why.
Integration Testing
Once you've tested your rule in isolation, you should include in within the intended Ruleset as part of the orchestration lifecycle logic, and perform end to end tests for various scenarios, each time using the Audit Events to validate the expected outcomes and behaviour.