Authors:
Ankit Mehta, Cille Schliebitz, Anita Gu
Changed on:
4 Feb 2025
In this module, you will learn about Unit Testing and creating a Mocked Unit test for a custom Rule. You will also learn how to perform testing on the TestExecutor and finally deploy your plugin into your sandbox environment
As Rules are atomic in size and complexity. We recommend a Test-Driven Development (TDD) approach. It is an approach where you first write tests, then use those tests to drive the design and development of your software application/rules. The TDD approach is also known as Red, Green, Refactor:
✅ Write a test (Red)
✅ Get the test to pass (Green).
✅ Optimise the design (Refactor)
Using this approach, Test cases are written for each Rules' functionality and are executed. In the event of a Test failing (I.e. not passing), a new code is written in order to pass the test by making the code simple and bug-free. This approach results in high-quality code.
We recommended that you adopt the following steps when performing testing on the Fluent Platform:
Fluent is currently using JUnit 4 as the Unit testing Framework — your plugin project pom.xml already has a dependency on JUnit.
💡 The above steps will be explained in the upcoming lessons of this section
⬇️ Scroll down to reveal more
TestExecutor
The TestExecutor simulates the Workflow Engine and allows developers to set up a mock Workflow for Unit Testing. This testing on TestExecutor helps to assert some additional behaviour of the Rule. Learn more in Testing Rules.
Remember this client?
This lesson relates to the Client Scenario — Implement Fraud Check covered in Module 4.
We have already created a Rule MarkOrderHighValueRule that implements Fraud Check, as per our client's requirement. In this lesson, we will create a Mocked Unit Test for the MarkOrderHighValueRule class. These tests are Ideal for TDD (Test Driven Development).
💡 Follow the steps below to create mocked unit tests for your custom GraphQL Rule
Important:
*After completing Step 1, your code should look similar to the one below:
1
2 package com.training.rule;
3
4 public class MarkOrderHighValueRuleTest
5 {
6
7 private MarkOrderHighValueRule rule = new MarkOrderHighValueRule();
8
9 @Mock
10
11 private Context context;
12
13 @Before
14
15 public void setup()
16 {
17
18 MockitoAnnotations.initMocks(this);
19
20 }
21
22 }
Language: plain_text
Name: MarkOrderHighValueRuleTest
Description:
Test class structure
The Test method uses the AAA (Arrange, Act, and Assert) approach, where you arrange the steps, act and then assert.
Let's create our first test case. We'll begin by asserting the expected behaviour for parameter validation.
The run method of MarkOrderHighValueRule class validates that Rule must have a valid parameter. Given below is the code snippet for validation from MarkOrderHighValueRule class:
1/**
2 * NOTE: This is a snippet from `MarkOrderHighValueRule class from 'Writing the Rules' section
3 **/
4
5// Validation:
6Integer threshold = context.getProp(Constants.PARAM_NAME_HIGH_VALUE_THRESHOLD, Integer.class);
7if (null == threshold)
8{
9 throw new PropertyNotFoundException(400,String.format("Required Parameter not provided: %s", Constants.PARAM_NAME_HIGH_VALUE_THRESHOLD));
10}
Language: plain_text
Name: MarkOrderHighValueRule class validation
Description:
Validate the rule property for threshold - PARAM_NAME_HIGH_VALUE_THRESHOLD
As shown in the above snippet, the run method should throw PropertyNotFoundException if the parameter is null. Our first test case will assert this behaviour of the run method.
Perform the steps below to create a test case:
* Your test case should Pass.
Learn more about AAA approach in the Test Case.
This video illustrates how to:
✅ Write a Test case.
✅ Import org.mockito package for the Mocking framework.
✅ Import com.fluentretail.rubix.v2.context.Context package for Context.
✅ Write a Test case to assert expected behaviour for parameter validation.
1<dependency>
2 <groupId>org.powermock</groupId>
3 <artifactId>powermock-module-junit4</artifactId>
4 <version>1.6.6</version>
5 <scope>test</scope>
6 </dependency>
7 <dependency>
8 <groupId>org.powermock</groupId>
9 <artifactId>powermock-api-mockito</artifactId>
10 <version>1.6.6</version>
11 <scope>test</scope>
12 </dependency>
Language: plain_text
Name: PowerMock dependencies
Description:
Snippet from the pom.xml about the required dependencies for Powermock
As the Event class is a final class, the org.mockito package can't mock it. We need to make use of PowerMock, it will make the final class use a different test runner and will prepare a class for the test.
Perform the steps below to add PowerMock into your plugin project:
Given below are the dependencies needed for the PowerMock:
⚠️ Make sure your IDE imports Maven changes after the maven's clean.
Annotation | Purpose |
@RunWith | E.g. @RunWith(PowerMockRunner.class) is used to define a different test runner for PowerMock |
@PrepareForTest | E.g. @PrepareForTest(Event.class) is used to prepare the runner for the Event class as the Event is the final class |
1// Required Mocks
2@Mock
3private Event event;
4@Mock
5private ReadOnlyFluentApiClient apiClient;
6@Mock
7private GetOrderByIdQuery.Data data;
8@Mock
9private GetOrderByIdQuery.OrderById orderById;
10@Mock
11private ActionFactory actionFactory;
Language: plain_text
Name: Required Mocks in testing class
Description:
Required mocked objects
Click on the + buttons to learn more about the setup() method for Powermock
This video illustrates how to:
✅ Add PowerMock to your plugin
✅ Define the test runner class and prepare the test class
✅ Define the required Mocks and the default behaviour of defined Mocks in the setup() method.
Let's begin with a simple test case:
"Verify that the Order is updated when the Total Price is above the Threshold"
For this test, we will verify that the MutationAction is queued via the Context's ActionFactory (context.action( ).mutate( )), with the GraphQL Mutation Object ( UpdateOrderAttributesMutation)
Perform the following steps:
*Your test case should Pass
Click on the + buttons to learn more about the required code for this test case:
This video illustrates how to:
✅ Write a test case to assert the Order is updated when the Total Price is above the Threshold.
✅ Run the test case using PowerMock defined and prepared runner class
Let us assert that "The Attribute is being built correctly."
We'll start with verifying the attribute name by mocking the AttributeInput.Builder class. This will allow us to verify that the attribute name is set correctly.
Perform the following steps:
@PrepareForTest({Event.class, AttributeInput.class})
@Mock
private AttributeInput.Builder mockedAttributeInputBuilder;
1@Test
2 public void run_withValueGreaterThanThreshold_addsAttributeWithNameIsHighValue() throws Exception {
3
4 //arrange:
5 when(orderById.totalPrice()).thenReturn(1001.00);
6 when(context.action()).thenReturn(actionFactory);
7
8 AttributeInput attributeInput = AttributeInput.builder().name("test-name").type("test-type").value("test-value").build();
9 PowerMockito.whenNew(AttributeInput.Builder.class).withNoArguments().thenReturn(mockedAttributeInputBuilder);
10 when(mockedAttributeInputBuilder.name(anyString())).thenReturn(mockedAttributeInputBuilder);
11 when(mockedAttributeInputBuilder.type(anyString())).thenReturn(mockedAttributeInputBuilder);
12 when(mockedAttributeInputBuilder.value(any())).thenReturn(mockedAttributeInputBuilder);
13 when(mockedAttributeInputBuilder.build()).thenReturn(attributeInput);
14
15 //act:
16 rule.run(context);
17
18 //assert:
19 verify(mockedAttributeInputBuilder, times(1)).name(Constants.IS_HIGH_VALUE_ORDER_ATTRIBUTE_NAME);
20 }
Language: plain_text
Name: PowerMock Test Case
Description:
Sample method to test if the high-value-attribute exists
This Video illustrates how to:
✅ Write a test case to assert The Attribute is being built correctly
Try writing the following test cases in the same project.
We recommend testing on TestExecutor before you upload your rule into your sandbox account. TestExecutor shows how the rule behaves when running in a local ThreadExecutor. It also asserts some additional behaviour.
The TestExecutor allows developers to setup a mock workflow for unit testing of specific rules and simulates the Workflow Framework and provides the following methods:
After execution, your tests can use the following for assertions:
In this section, we will test our Rule in the TestExecutor. We will run the Rule under the following conditions, and assert the expected outcomes:
In the src/test/java/com.training/rule folder of your plugin project, create a new class named MarkOrderHighValueRuleExecutorTest .
Declare fields, mocks, and setup() method as given in the code snippet below.
1public class MarkOrderHighValueRuleExecutorTest {
2
3 private static final Integer HIGH_VALUE_THRESHOLD = 1000;
4 private static final String ORDER_ID = "123";
5
6 private TestExecutor executor;
7
8 private Event event;
9
10 @Mock
11 private ReadOnlyFluentApiClient apiClient;
12
13 @Mock
14 private GetOrderByIdQuery.Data data;
15
16 @Before
17 public void setup() {
18
19 MockitoAnnotations.initMocks(this);
20
21 when(apiClient.query(any())).thenReturn(data);
22 }
23}
Language: plain_text
Name: MarkOrderHighValueRuleExecutorTest
Description:
Unit Test class with TestExecutor setup
In the setup method, create an instance of a RubixEntity. This is an orchestratable entity inside of Rubix, which is made available to the Rule via the Context.
Given below is the code snippet to create an entity
1@Before
2 public void setup() {
3
4 MockitoAnnotations.initMocks(this);
5
6 when(apiClient.query(any())).thenReturn(data);
7
8 // Setup up an instance of a Rubix Entity
9 RubixEntity entity = RubixEntity.builder()
10 .status("BOOKED")
11 .entityType("ORDER")
12 .flexType("flexType")
13 .flexVersion(1)
14 .ref(ORDER_ID)
15 .id(UUID.randomUUID().toString())
16 .build();
17
18 }
Language: plain_text
Name: MockitoAnnotations
Description:
Create Rubix Entity with the required fields set
In the setup method, prepare the Event that will be used to trigger the workflow. Refer to the below interaction, click on + buttons to learn more about the code
Click the Play Button ▶️ to begin the video.
This video illustrates how to:
✅ Write a test class
✅ Create an Event entity, and an Event to trigger the Ruleset
We need to provide the following to TestExecutor to execute the test:
Do the following to create a Ruleset:
In the setup() method, initialise the TestExecutor to simulate the Workflow Engine using the code snippet below:
1// Set up a TestExecutor to simulate Orchestration Engine
2 executor = TestExecutor.builder()
3 .rule(MarkOrderHighValueRule.class)
4 .ruleset(ruleSet)
5 .entity(entity)
6 .testApiClient(apiClient)
7 .build();
Language: plain_text
Name: Initialise TestExecutor
Description:
Build the TestExecutor object
We have our TestExecutor prepared. Follow the steps to implement the test case.
1@Test
2 public void run_withTotalPriceGreaterThanThreshold_callsMutateAction() {
3
4 // arrange:
5 GetOrderByIdQuery.OrderById orderById = GetOrderByIdQuery.OrderById.builder().id(ORDER_ID).totalPrice(1001.00).type("type").__typename("type-name").build();
6
7 when(data.orderById()).thenReturn(orderById);
8
9 executor.validateWorkflow(event);
10
11 // act:
12 TestContext context = executor.execute(event);
13
14 // assert:
15 assertEquals(1, context.countActionsOfType(TestContext.MutateAction.class));
16 }
Language: plain_text
Name: AAA approach (arrange, act, and assert)
Description:
Sample Test method to execute the UnitTest
This Video illustrates how:
✅ Create Rule instance and Ruleset
✅ Setup a TestExecutor
✅ Write a test case to run on TestExecutor
Try writing the following test cases in the same project.
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.
Copyright © 2025 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.