Rules SDK Testing Rules
Essential knowledge
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)
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`
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}The TestExecutor
The`TestExecutor` allows developers to set up a mock workflow for unit testing of specific rules and simulates the Rubix Orchestration Engine.It provides the following methods:`rule(Class.class)`: load a rule`ruleset(RuleSet ruleSet)`: adds a ruleset to the workflow`entity(Entity entity)`: adds an entity to the workflow`validateWorkflow(Event event)`: validates the workflow with the given event`execute(Event event)`: executes the workflow with the given event
The `TestContext`
The `TestContext` provides functionality to retrieve results post-workflow execution.`action()`: Provides the action interfaces of the fluent retail API client.`api()`: Provides the API interfaces of the fluent retail API client.`count*()`: Provides access to the number of events or actions that have been executed in the context.`get*()`: Provides access to the entities, rules, events, and properties.
The `scanAndValidateAllRules()` Method
The `scanAndValidateAllRules()` unit test provided with the SDK, and should be present in each plugin. This validates the accuracy of the rule annotations and parameters.