MUnit testing strategies for complex MuleSoft scenarios
- July 04, 2023
MUnit is a testing framework provided by MuleSoft that enables developers to comprehensively and efficiently test Mule applications and integrations.
In this article, we’ll explore MUnit testing strategies for complex scenarios. We’ll cover advanced techniques for testing MuleSoft integrations that involve complex flows, APIs, connectors and more.
1. Choice router
The choice router in MuleSoft is used to conditionally route messages through different parts of a flow based on a set of defined conditions. We can think of it as a series of if-else cases.
Many choose to create separate flows for each condition of the choice router. However, that’s not an efficient approach.
In our use case, we compare the payload’s value. If the value is greater than 80, it’ll be set to “Best”. If the value is between 60 and 80, it’ll be set to “Average.” Otherwise, the default value is “Fail.”
Here, we’ll explore two parameterization approaches. First, we need to create a blank test for the flow.
Without the YAML file:
In the MUnit configuration, we’ll add parameterization by specifying parameter names and values. The parameterization names can be chosen freely and aren’t used elsewhere. The parameter name and value keys will be dynamically passed as keys in the “Set Event” and “Assert Equals” components.
You can locate the MUnit configurations in the Global Element section of the MUnit Test Suite file, located under src/test/munit.
Since we’ve designated the parameter names as “input” and “output,” the “input” represents the payload’s value being compared within the condition, while the “output” represents the value being set inside the “when” block, such as the “set payload” operation in our case.
- Within the MUnit Test flow, in the Behavior section, add a Set Event to provide a dynamic key for the input.
- In the Execution section, add a flow reference component and refer it to our flow.
- In the Validation Section, add an Assert Equals to check the value of the payload with the output that’s been parameterized.
The overall flow of the MUnit test suite will be as follows:
With YAML file:
In MUnit configurations, there’s another option to achieve parameterization by referencing a YAML file. Within this file, we can provide the same parameters that were externalized in our previous step.
Create a YAML file with any desired name and parameters (let’s say) in the src/test/resources directory.
The MUnit flow will remain the same as we created it for the case of externalizing parameters (without using the YAML file).
After running the MUnit test flow, it’ll cover each choice router block one by one.
Note: It doesn’t maintain the sequence of execution and can run any condition first.
2. Batch process
Results and processing for batch jobs are contained within the batch job itself. Since batch results are not stored anywhere other than the batch job execution, it’s not possible to validate batch successes or failures as you would with other components. Mule 4’s batch jobs behave in this way, hence special processing needs to be implemented to handle these situations.
If you’re working with a simple flow that includes a Batch job, the following steps will guide you on how to validate the number of successfully processed records.
In our flow, we initially have a payload consisting of an array of four numbers. Following that, we have a batch step that employs a set payload operation to increment each incoming record by 1. Within the batch aggregator and on complete stages, we include a logger component that logs the value of the payload.
- Create a blank MUnit test for this flow. In the behavior section, add a Spy processor from the MUnit Tools Module in the palette.
- In the Spy Configuration, select a batch-step processor.
- In the Spy’s “Before call” section, add an Assert That operator. Here, we’ll provide the expression as payload.stepResults[0].successfulRecords, and it should be equal to MunitTools::nullValue(), as the number of successful records before execution is 0.
- In the Spy’s “After call” section, add an Assert That operator. Here, we’ll provide the expression as payload.stepResults[0].successfulRecords, and it should be equal to MunitTools::greaterThanOrEqualTo(4), as there are four records in our array. After execution, the number of successful records should be four.
- In the Execution section, add a flow reference component and refer it to our flow.
- After the flow reference, add a sleep connector from the MUnit Tools module in the palette. This is necessary because Batch is asynchronous and multiple threads run concurrently, while MUnit is synchronous. To ensure the completion of each thread, a wait time must be provided; otherwise, the test may fail. Provide a value based on the time your batch jobs take. In this case, we’ll use 10 seconds.
- In the Validation section, add a verify call and select the payload processor that’s inside the batch step. Provide a quantity equal to four.
The overall flow of the MUnit test suite will be as follows:
After running the test suite, MUnit is successful.
Let’s consider one more scenario where we want to handle the failed records and perform some processing on them in the second batch step, such as moving them to an Anypoint MQ queue.
In our flow, there’s a payload consisting of numbers and character values. We have a batch job configured with ‘Max Failed Records’ set as ‘-1’, which means this batch job will be capable of handling any number of failed records.
Here, we have two batch steps. In the event that any failed records are generated from batch step 1, they will be handled and aggregated by batch step 2. To achieve this, configure the ‘Accept Policy’ as ‘ONLY_FAILURES’.
As you can see, in the ‘Set Payload’ operation, we have data stored in an array that includes two characters. Inside the batch step, we are performing an arithmetic operation (+). Consequently, this would result in failed records.
Now, create a blank MUnit test for this flow.
In the Spy’s ‘Before call’ section, add an Assert that connector with the value:
In the Spy’s ‘After call’ section, add an Assert That connector with the following value. Since we know that the number of failed records would be two, provide the appropriate configuration:
After running the test suite, the Batch Job will be successfully mocked.
3. For-Each
The For-Each scope divides a payload into elements and processes each one individually through the components within the scope. It can handle any collection, including lists and arrays, and is comparable to a for-each/for loop code block in most programming languages.
To test a For-Each scope in MUnit, there are two approaches. One approach is to provide a single data inside an array and pass it into the event processor. Inside the event processor, the actual processor within the For-Each scope can be mocked. This ensures that it doesn’t iterate over the array and runs only once, satisfying the For-Each condition and functioning properly.
The other method will tackle the problem of looping and will create a test flow in such a way that will be successful for different responses coming out of looping.
Let’s consider the example of the following flow. It starts with a Transform Message component that contains an array of JSON objects. (In your case, it can be any other processor that returns data, such as a Salesforce or database connector.) After the Transform Message, we have a For-Each scope. Inside the For-Each scope, there’s an HTTP Request connector configured to store its payload in a variable called ‘target’, followed by a Transform Message component. After this scope, there’s another Transform Message component to display the value of the ‘target’ variable. Lastly, there’s a Logger component that logs the value of the payload.
First, right-click on the flow and choose MUnit. Then, click on “Record Test for This Flow.”
After the processing, to start the recording, run the flow by hitting the endpoint of this flow. Next, click on the “Configure Test” button.
Then a wizard will be displayed to configure the settings.
Choose a file name for your for-each test suite, and then click Next.
Here, we’re only mocking the request connector inside the For-Each. Click Next.
Now, a MUnit file will be created with two flows. We don’t need to configure anything at this step. It would look like the example below:
If you open the ‘Mock When’ configuration, you’ll be able to see that a ‘then call’ has been configured, which will further call the ‘mock-http:request’ flow.
Whenever the event processing reaches the request connector of the For-Each, it will mock and call this ‘then call’ flow.
In the ‘mock-http:request’ flow, we have a try block that contains the MUnit Tools ‘retrieve’ connector. This connector checks the count of the loop. Initially, the retrieve connector does not have any data, so it throws an error: “Unable to find key: Mock Request”. The execution then moves to the error handling part, which is set to ‘on-error continue’, and assigns the value of count(payload) to 1.
Then, this value of count will be stored by the MUnit Tools ‘store’ connector. The execution will run for each element of the array, processing them individually.
The validation part of the ‘for-eachFlow-test’ flow contains an ‘Assert Payload’ component, which will verify the end result of the flow.
After running the test suite, the For-Each scope will be successfully mocked, and the test case will iterate over the collection of messages.
4. FTP
The FTP Connector (Anypoint Connector for FTP) allows you to access files and directories on an FTP server. The File Transfer Protocol (FTP) is used by FTP connector operations to manage file transfers.
Below is our simple flow for FTP. We’re using an FTP List connector, where directory and fileName patterns are specified. Both directory and fileName are received through query parameters.
- Create a new MUnit test suite for FTP.
- In the Behavior section, add a Set Event and Mock When from the MUnit module and MUnit Tools module, respectively, from the palette.
- In the Set Event, we’ll configure the attributes that the FTP connector is expected to receive, i.e., directory and fileName.
As we’ve specified the fileName Pattern in the FTP Configuration, it will be taken from the query parameters. So, we pass that attribute as shown below:
In the Mock When connector, we’ll mock the FTP connector and provide the expected payload that’s returned by FTP. We pick the FTP processor in the Mock When connector, and here we create a JSON file with the expected response. This file should be placed in src/test/resources. The fileName attribute should follow the fileName Pattern.
- In the Execution section, add a flow reference component and refer it to our flow.
- In the Validation Section, add a Verify call and pick the last processor, which is the logger in our case and provide its values to 1, to verify that our test has been executed until the end of our flow successfully.
The overall flow of the MUnit test suite will be as follows:
After running the test suite, FTP will be successfully mocked.
— By Anjali Kushwaha & Ashish Singh Chauhan