MUnit Testing Done Right

Written by:
Published on April 1, 2021

MUnit Testing Done Right

Writing unit test cases is the key requirement of any MuleSoft implementation.

But in many organizations, you end up skipping to write the unit test cases in MuleSoft applications – since it is considered as too time-consuming or is too complex to write. And even in cases where test cases are written, they are generic MUnit test cases auto-generated by the studio – which won’t be updated and hence aren’t that useful. We are going to explore the proper way to implement a standard testing process in MuleSoft – which should bring down the effort way down and reap the benefits the testing is supposed to deliver.


The essence of any unit testing is to make sure that all the aspects of the application/API generated is working as expected. This becomes extremely crucial in an API world because all the APIs in the enterprise should behave the same way (ie. The way APIs communicate error responses, common headers to be used, etc). Even though API specifications re-use a common API template for design, wouldn’t it make sense to have a common test case that can be re-used for the majority of API implementations – and require very minimal test case tweaking to get it completed?

Previously we had to write custom testing framework to accomplish this – but with the parameterized testing feature of MUnit ( ), most of the custom implementation could be replaced with out of the box features. Let’s see how this works with an example!

Step 1: Understand the Enterprise API standards to be tested

Each customer ends up using different versions of API specification templates and in most of the cases, end up customizing the template to meet their needs. But the most common thing all enterprises will have is how the errors and API responses will be handled. For example, all APIs should have a common error structure and message format.

For the purpose of this blog, we will use a common structure for error message to look like below:

  "header" : {  
    "apiName" : "mule-api-template",  
    "apiVersion" : "1.0.0",  
    "correlationId" : "9d940080-7b4e-11eb-8e34-acde48001122"  
  "errors" : [ {  
    "description" : "There was an issue with your request message. Required header 'x-correlation-id' not specified",  
    "code" : 400,  
    "timestamp" : "2021-03-02T11:58:35.302Z"  
  } ]  

The following are the key error codes being used for representational purposes:

HTTP Response Code Description
200 Success for normal API calls
200 Success for API Consoles
400 Bad request
404 Not implemented
406 Not acceptable
415 Unsupported Media Type
500 Server Error
501 Method not allowed

We also add a custom mandatory HTTP header to all incoming requests – like X-Correlation-Id

And finally, we will re-use existing mule implementation template available at

Customization of error handler plugin is not covered as part of this post.

Step 2: Create base MUnit test case to test all the enterprise specifications in a generic manner

Now the fun bit. Create a simple test case that has all its features parameterized.

In this example below, we have set up a simple test case that does the following:

  1. Get payload from a parameterized file
  2. Call API as a request – with the required parameters
  3. Check the status code to be as expected
  4. Do additional checks based on how the test case needs to be customized for the organization

Let’s see some of the key configurations from the test case:

HTTP Requester component:

Status code assertion:

Please find below the sample test case implementation code:

1.	<?xml version="1.0" encoding="UTF-8"?>  
3.	<mule xmlns:http=""  
4.	    xmlns:xsi="" xmlns:munit=""  
5.	    xmlns:munit-tools="" xmlns=""  
6.	    xmlns:doc="" xsi:schemaLocation="  
11.	    <munit:config name="new-test-suite.xml" >  
12.	        <munit:parameterizations file="test-parameters.yaml" />  
13.	    </munit:config>  
14.	    <http:request-config name="New_HTTP_Request_configuration" doc:name="HTTP Request configuration" doc:id="77b91123-f998-43ae-a38f-df4166d6c310">  
15.	        <http:request-connection host="${}" port="${http.port}" />  
16.	    </http:request-config>  
17.	    <munit:dynamic-port doc:name="MUnit dynamic port" doc:id="353d02e3-6e35-4e8d-a57e-f64f87ceebb1" propertyName="http.port" />  
18.	    <global-property doc:name="Global Property" doc:id="ee62529c-0cab-4465-8841-ef23b13c1d2e" name="mule.env" value="dev" />  
19.	    <global-property doc:name="Global Property" doc:id="15387adf-e4b9-42a6-8348-dccb7db6eb3d" name="mule.encryption.key" value="1234567812345678" />  
20.	    <munit:test name="global-api-test" doc:id="50c9db34-1d37-4536-8a8a-b3c9ec63e597" description="Test">  
21.	        <munit:enable-flow-sources >  
22.	            <munit:enable-flow-source value="api-template-console" />  
23.	            <munit:enable-flow-source value="api-template-main" />  
24.	            <munit:enable-flow-source value="post:\template-test:api-template-config" />  
25.	            <munit:enable-flow-source value="implementationFlow" />  
26.	        </munit:enable-flow-sources>  
27.	        <munit:execution>  
28.	            <set-payload value="#[output application/json --- readUrl(Mule::p('payloadUrl'))]" doc:name="Set Payload" doc:id="591eeb07-14be-4f48-bda6-b4d23e25dd49" mimeType="application/json"/>  
29.	            <http:request method="${method}" doc:name="Request" doc:id="a4b244ae-cd8a-41f0-a48b-150b16fc7cb3" config-ref="New_HTTP_Request_configuration" sendCorrelationId="#[if(sizeOf(p('correlationId'))>0)'ALWAYS' else 'NEVER']" path="${url}" outputMimeType="application/json" correlationId="${correlationId}">  
30.	                <http:headers ><![CDATA[#[output application/java 
31.	--- 
32.	{ 
33.	    "content-type" : p('contentType'), 
34.	    "accept" : p('accept') 
35.	}]]]></http:headers>  
36.	                <http:response-validator >  
37.	                    <http:success-status-code-validator values="#[${httpStatus}]" />  
38.	                </http:response-validator>  
39.	            </http:request>  
40.	        </munit:execution>  
41.	        <munit:validation >  
42.	            <munit-tools:assert-that expression="#[attributes.statusCode]" is="#[MunitTools::equalTo(${httpStatus})]" message="The HTTP Status code is not correct!" doc:name="Assert That Status Code is as expected" />  
43.	            <choice doc:name="Choice" doc:id="d1e78f92-22d8-4528-a057-711c938fba32" >  
44.	                <when expression="#[p('assertNonStatus') == 'true']">  
45.	                    <logger level="INFO" doc:name="Logger" doc:id="271da83a-4ff9-46b9-9ef1-3a39b228fbda" />  
46.	                    <munit-tools:assert-that doc:name="Assert that Correlation ID is as expected" doc:id="448e3ac6-0d92-4dc0-a4fb-ebeabab88a51" is="#[MunitTools::equalTo(if(sizeOf("${correlationId}") > 0) "${correlationId}" else attributes.'X-Correlation-Id')]" expression="#[attributes.headers.'X-Correlation-Id']" message="Correlation ID is not as expected!"/>  
47.	                    <munit-tools:assert-equals doc:name="Assert that error codes are as expected" doc:id="b7412c11-1870-4ab2-a400-a71cea38e293" actual="#[payload.errors[0].description]" expected="${description}" message="Error Codes are not matching!" />  
48.	                </when>  
49.	                <otherwise >  
50.	                    <logger level="INFO" doc:name="Logger" doc:id="33a4e9fb-fad7-498d-b1c5-cfe50843108a" message="Test Completed"/>  
51.	                </otherwise>  
52.	            </choice>  
53.	            </munit:validation>  
54.	    </munit:test>  
56.	</mule>

Step 3: Create parameterized properties

Create a structure which works based on the test case you have created. For example, for the simple test case above, we are using the parameter that looks like below:

1.	200-success:  
2.	    url: "/api/v1/template-test"  
3.	    method: "POST"  
4.	    correlationId: "success-correlation-id"  
5.	    accept: "application/json"  
6.	    contentType: "application/json"  
7.	    httpStatus: "200"  
8.	    assertNonStatus: "false"  
9.	    description: ""  
10.	    payloadUrl: "classpath://set-event_payload.dwl" 

As can be noticed, the parameters set here are exactly what will be used in the test case message processors. Once we test for a single parameter, we can add all the different parameters to test out all the different combinations – for all the error types of the API.

Find below the sample configuration that you could use to simulate the key errors listed for this example:

1.	200-console:  
2.	    url: "/console"  
3.	    method: "GET"  
4.	    destinationType: "#docs"  
5.	    destinationName: "summary/summary"  
6.	    correlationId: "success-correlation-id"  
7.	    accept: "*/*"  
8.	    contentType: "application/json"  
9.	    httpStatus: "200"  
10.	    assertNonStatus: "false"  
11.	    errorCode: ""  
12.	    payloadUrl: "classpath://set-event_payload.dwl"  
14.	200-success:  
15.	    url: "/api/v1/template-test"  
16.	    method: "POST"  
17.	    correlationId: "success-correlation-id"  
18.	    accept: "application/json"  
19.	    contentType: "application/json"  
20.	    httpStatus: "200"  
21.	    assertNonStatus: "false"  
22.	    description: ""  
23.	    payloadUrl: "classpath://set-event_payload.dwl"  
26.	400-bad-request-no-correlation-id:  
27.	    url: "/api/v1/template-test"  
28.	    method: "POST"  
29.	    correlationId: ""  
30.	    accept: "application/json"  
31.	    contentType: "application/json"  
32.	    httpStatus: "400"  
33.	    assertNonStatus: "false"  
34.	    description: "There was an issue with your request message. Required header 'x-correlation-id' not specified"  
35.	    payloadUrl: "classpath://set-event_payload.dwl"  
38.	404-not-implemented-url:  
39.	    url: "/api/v1/template-test2"  
40.	    method: "POST"  
41.	    correlationId: "success-correlation-id"  
42.	    accept: "application/json"  
43.	    contentType: "application/json"  
44.	    httpStatus: "404"  
45.	    assertNonStatus: "true"  
46.	    description: "The API has not been implemented /template-test2"  
47.	    payloadUrl: "classpath://set-event_payload.dwl"  
51.	406-not-acceptable-accept:  
52.	    url: "/api/v1/template-test"  
53.	    method: "POST"  
54.	    correlationId: "success-correlation-id"  
55.	    accept: "application/xml"  
56.	    contentType: "application/json"  
57.	    httpStatus: "406"  
58.	    assertNonStatus: "true"  
59.	    description: "One of the request or parameters is unacceptable Not Acceptable"  
60.	    payloadUrl: "classpath://set-event_payload.dwl"   
63.	415-unsupported-media-type:  
64.	    url: "/api/v1/template-test"  
65.	    method: "POST"  
66.	    correlationId: "success-correlation-id"  
67.	    accept: "application/json"  
68.	    contentType: "application/xml"  
69.	    httpStatus: "415"  
70.	    assertNonStatus: "true"  
71.	    description: "Media Type not supported Unsupported mediaType"  
72.	    payloadUrl: "classpath://set-event_payload.dwl"  
75.	500-server-error:  
76.	    url: "/api/v1/template-test"  
77.	    method: "POST"  
78.	    correlationId: "success-correlation-id"  
79.	    accept: "application/"  
80.	    contentType: "application/json"  
81.	    httpStatus: "500"  
82.	    assertNonStatus: "true"  
83.	    description: "Internal Server Error"  
84.	    payloadUrl: ""      
88.	501-method-not-allowed:  
89.	    url: "/api/v1/template-test"  
90.	    method: "GET"  
91.	    correlationId: "success-correlation-id"  
92.	    accept: "application/json"  
93.	    contentType: "application/json"  
94.	    httpStatus: "501"  
95.	    assertNonStatus: "true"  
96.	    description: "The method has not been implemented HTTP Method get not allowed for : /template-test"  
97.	    payloadUrl: "classpath://set-event_payload.dwl"  

Step 4: Include the sample test case as part of your mule template

Organizations should ideally be following (and hopefully most are) following a set mule template which simplifies by putting all the best practices into a template – which should reduce more than 60% of all the implementation effort. See an example of such a mule code template at

Put the generated enterprise test case as part of this template – so that every project which implements using this template will have the generic test case added as part of it.

Step 5: Customize the test case based on API being implemented

Once the mule application is generated based on the code template, a base standard test case will already be present in the implementation. The only things you would need to change would be which resources to call and what error codes to test for. Note that even these can be automated (and should be in large organizations!) – which is out of scope of this article. Let’s say the API being implemented has a call to Anypoint MQ (or any other connector/system), you could add API specific behavior like below:

In this use case, we are conditionally mocking the Anypoint MQ connector – to provide success message for certain scenarios and to throw specific errors for other scenarios. In other words, the test cases can be customized as you need it for any use case being implemented.

When you run the test case you should now see the test case running for every parameter like below:

You can check the coverage of the test cases:

Effort involved in customization should be 80-90% less than creating/generating basic test cases – since everything is templated! Another benefit of this approach would be that you could even govern and track compliance using a product like IZ Analyzer ( ).

Parting thoughts

MuleSoft continues to enhance its existing features regularly to incorporate best practices and standards to suit wider group of customers and organizations. One of the drawbacks of such a fast-paced innovation is the fact that many of the features will either go unnoticed or lack documentation/tutorials to highlight the actual benefits the feature brings to the table. Architects and Leads should always strive to keep themselves up-to-date with all the major features being added to make sure that we can bring the benefits back to the organizations! This holds true especially for features like MUnit which is perceived to be the hardest and most troublesome by most developers. Hopefully, this feature will ease the burden of the developers by 70-80% and help in boosting productivity and increase compliance!

Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *

Other Blog Posts

Other Blog Posts

MuleSoft Runtime Code Scanning – Why Do You Need It?

One of the most frequently asked questions is if we have static code analysis and a well defined DevOps process, why would we need run time code analysis? In this article, let’s explore the differences between the two and why you might want to have runtime code analysis (and IZ Runtime Analyzer) even if you have …

Read more

Ensuring Software Quality in Healthcare: Leveraging IZ Analyzer for MuleSoft Code Scanning 🏥💻

Ensuring software quality in the healthcare industry is a top priority, with direct implications for patient safety, data security, and regulatory compliance. Healthcare software development requires adherence to specific rules and best practices to meet the unique challenges of the industry. In this blog post, we will explore essential software quality rules specific to healthcare …

Read more

Mule OWASAP API Security Top 10 – Broken Object Level Authorization

In Mule, Object-Level Authorization refers to the process of controlling access to specific objects or resources within an application based on the permissions of the authenticated user. It ensures that users can only perform operations on objects for which they have appropriate authorization. To demonstrate a broken Object-Level Authorization example in Mule, let’s consider a …

Read more

How KongZap Revolutionises Kong Gateway Deployment

In a rapidly evolving digital landscape, businesses face numerous challenges. Faster time to market is the only option business can choose. When it comes end to end Kong Gateway life cycle from deploying to managing Kong Gateway, every one of these challenges is applicable. However, KongZap, a groundbreaking solution is a game-changer by addressing some …

Read more