Fluent Commerce Logo
Docs
Sign In

Rules SDK Testing Rules

Essential knowledge

Author:

Fluent Commerce staff

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`
 

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`
 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.

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.

Fluent Commerce staff

Fluent Commerce staff

Copyright © 2024 Fluent Retail Pty Ltd (trading as Fluent Commerce). All rights reserved. No materials on this docs.fluentcommerce.com site may be used in any way and/or for any purpose without prior written authorisation from Fluent Commerce. Current customers and partners shall use these materials strictly in accordance with the terms and conditions of their written agreements with Fluent Commerce or its affiliates.

Fluent Logo