Nav

MUnit Short Tutorial

Overview

This simple, short tutorial takes you through the process of creating a series of MUnit tests, which are aimed to validate the behavior of a simple code example.

This tutorial uses only core components of Mule ESB. No Anypoint Connectors are used; however, you can easily apply what you learn here to Anypoint Connectors.

Sample Production Code

The sample code for this tutorial is fairly simple, but it uses some of Mule’s most common message processors. It implements a basic use case:

  1. It receives an HTTP request.

  2. It extracts data from the request to route a message through the application.

  3. It decides how to create a response.

exampleFlow


    
            
         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:http="http://www.mulesoft.org/schema/mule/http"
        xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns="http://www.mulesoft.org/schema/mule/core"
        xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
        xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.7.3"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">

    <http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="9090" doc:name="HTTP Listener Configuration"/>

    <flow name="exampleFlow">
        <http:listener config-ref="HTTP_Listener_Configuration" path="/" allowedMethods="GET" doc:name="HTTP"/>
        <set-payload value="#[message.inboundProperties['http.query.params']['url_key']]" doc:name="Set Original Payload"/>

        <flow-ref name="exampleFlow2" doc:name="exampleFlow2"/>


        <choice doc:name="Choice">
            <when expression="#[flowVars['my_variable'].equals('var_value_1')]">
                <set-payload value="#['response_payload_1']" doc:name="Set Response Payload"/>
            </when>
            <otherwise>
                <set-payload value="#['response_payload_2']" doc:name="Set Response Payload"/>
            </otherwise>
        </choice>
    </flow>

    <flow name="exampleFlow2">
        <choice doc:name="Choice">
            <when expression="#['payload_1'.equals(payload)]">
                <flow-ref name="exampleSub_Flow1" doc:name="exampleSub_Flow1"/>
            </when>
            <otherwise>
                <flow-ref name="exampleSub_Flow2" doc:name="exampleSub_Flow2"/>
            </otherwise>
        </choice>
        </flow>

    <sub-flow name="exampleSub_Flow1">
        <set-variable variableName="my_variable" value="#['var_value_1']" doc:name="my_variable"/>
    </sub-flow>

    <sub-flow name="exampleSub_Flow2">
        <set-variable variableName="my_variable" value="#['var_value_2']" doc:name="my_variable"/>
    </sub-flow>
</mule>

We analyze the above code by breaking it up into sections. The first section is the entry point of the application, which contains the HTTP listener in exampleFlow.

part-1

  1. Main Flow, exampleFlow.

  2. Take data from the HTTP request.

  3. Make call to exampleFlow2 for further processing.

  4. Make a decision based on the value of the invocation variable my_variable, which was set by exampleFlow2.

  5. Creates response payload 1.

  6. Creates response payload 2.


    
            
         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
&lt;flow name="exampleFlow"&gt;                                                                                                 <i class="conum" data-value="1"></i><b>(1)</b>
    &lt;http:listener config-ref="HTTP_Listener_Configuration" path="/" allowedMethods="GET" doc:name="HTTP"/&gt;

    &lt;set-payload value="#[message.inboundProperties['http.query.params']['url_key']]" doc:name="Set Original Payload"/&gt;   <i class="conum" data-value="2"></i><b>(2)</b>

    &lt;flow-ref name="exampleFlow2" doc:name="exampleFlow2"/&gt;                                                               <i class="conum" data-value="3"></i><b>(3)</b>

    &lt;choice doc:name="Choice"&gt;                                                                                            <i class="conum" data-value="4"></i><b>(4)</b>
        &lt;when expression="#[flowVars['my_variable'].equals('var_value_1')]"&gt;
            &lt;set-payload value="#['response_payload_1']" doc:name="Set Response Payload"/&gt;                                <i class="conum" data-value="5"></i><b>(5)</b>
        &lt;/when&gt;
        &lt;otherwise&gt;
            &lt;set-payload value="#['response_payload_2']" doc:name="Set Response Payload"/&gt;                                <i class="conum" data-value="6"></i><b>(6)</b>
        &lt;/otherwise&gt;
    &lt;/choice&gt;
&lt;/flow&gt;

<1> Main Flow, exampleFlow. <2> Take data from the HTTP request. <3> Make call to exampleFlow2 for further processing. <4> Make a decision based on the value of the invocation variable my_variable, which was set by exampleFlow2. <5> Creates response payload 1. <6> Creates response payload 2.

The second part of the app, exampleFlow2, is basically for routing. It does not perform any real action over the message or its payload. Rather, it delegates that to two other subflows, based on the payload received as input.

part-2

  1. Evaluate the payload that enters the flow.

  2. Make call to exampleSub_Flow1.

  3. Make call to exampleSub_Flow2.


    
            
         
1
2
3
4
5
6
7
8
9
10
&lt;flow name="exampleFlow2"&gt;
    &lt;choice doc:name="Choice"&gt;
        &lt;when expression="#['payload_1'.equals(payload)]"&gt;                                          <i class="conum" data-value="1"></i><b>(1)</b>
            &lt;flow-ref name="exampleSub_Flow1" doc:name="exampleSub_Flow1"/&gt;                         <i class="conum" data-value="2"></i><b>(2)</b>
        &lt;/when&gt;
        &lt;otherwise&gt;
            &lt;flow-ref name="exampleSub_Flow2" doc:name="exampleSub_Flow2"/&gt;                         <i class="conum" data-value="3"></i><b>(3)</b>
        &lt;/otherwise&gt;
    &lt;/choice&gt;
&lt;/flow&gt;

<1> Evaluate the payload that enters the flow. <2> Make call to exampleSub_Flow1. <3> Make call to exampleSub_Flow2.

Finally we have the various subflows called exampleSub_Flow<number>, whose only task is to set a value for an invocation variable named my_variable.

part-3

  1. Set my_variable to var_value_1.

  2. Set my_variable to var_value_2.


    
            
         
1
2
3
4
5
6
7
&lt;sub-flow name="exampleSub_Flow1"&gt;
    &lt;set-variable variableName="my_variable" value="#['var_value_1']" doc:name="my_variable"/&gt;    <i class="conum" data-value="1"></i><b>(1)</b>
&lt;/sub-flow&gt;

&lt;sub-flow name="exampleSub_Flow2"&gt;
    &lt;set-variable variableName="my_variable" value="#['var_value_2']" doc:name="my_variable"/&gt;    <i class="conum" data-value="2"></i><b>(2)</b>
&lt;/sub-flow&gt;

<1> Set my_variable to var_value_1. <2> Set my_variable to var_value_2.

Creating Tests

Below is the MUnit Test Suite file:

full-test-example


    
            
         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
&lt;?xml version="1.0" encoding="UTF-8"?&gt;

&lt;mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:mock="http://www.mulesoft.org/schema/mule/mock"
        xmlns:munit="http://www.mulesoft.org/schema/mule/munit" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
        xmlns:spring="http://www.springframework.org/schema/beans" xmlns:core="http://www.mulesoft.org/schema/mule/core"
        version="EE-3.7.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.mulesoft.org/schema/mule/mock http://www.mulesoft.org/schema/mule/mock/current/mule-mock.xsd
http://www.mulesoft.org/schema/mule/munit http://www.mulesoft.org/schema/mule/munit/current/mule-munit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd"&gt;

    &lt;munit:config name="munit" doc:name="Munit configuration"/&gt;

    &lt;spring:beans&gt;
        &lt;spring:import resource="classpath:demo.xml"/&gt;
    &lt;/spring:beans&gt;

    &lt;!-- exampleFlow2 Tests --&gt;
    &lt;munit:test name="doc-test-exampleFlow2Test1" description="Validate calls to sub flows are being done properly "&gt;
        &lt;munit:set payload="#['payload_1']" doc:name="Set Message payload == payload_1"/&gt;
        &lt;flow-ref name="exampleFlow2" doc:name="Flow-ref to exampleFlow2"/&gt;
        &lt;mock:verify-call messageProcessor="mule:sub-flow" doc:name="Verify Call" times="1"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#[matchContains('exampleSub_Flow1')]" name="name"/&gt;
            &lt;/mock:with-attributes&gt;
        &lt;/mock:verify-call&gt;
    &lt;/munit:test&gt;

     &lt;munit:test name="doc-test-exampleFlow2Test2" description="Validate calls to sub flows are being done properly "&gt;
        &lt;munit:set payload="#['payload_2']" doc:name="Set Message payload == payload_2"/&gt;
        &lt;flow-ref name="exampleFlow2" doc:name="Flow-ref to exampleFlow2"/&gt;
        &lt;mock:verify-call messageProcessor="mule:sub-flow" doc:name="Verify Call" times="1"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#[matchContains('exampleSub_Flow2')]" name="name"/&gt;
            &lt;/mock:with-attributes&gt;
        &lt;/mock:verify-call&gt;
    &lt;/munit:test&gt;

    &lt;!-- exampleFlow Tests --&gt;
    &lt;munit:test name="doc-test-exampleFlow-unit-Test_1" description="Unit Test case asserting scenario 1"&gt;
        &lt;mock:when messageProcessor="mule:set-payload" doc:name="Mock"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#['Set Original Payload']" name="doc:name"/&gt;
            &lt;/mock:with-attributes&gt;
            &lt;mock:then-return payload="#[]"/&gt;
        &lt;/mock:when&gt;
        &lt;mock:when messageProcessor="mule:flow" doc:name="Mock"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#['exampleFlow2']" name="name"/&gt;
            &lt;/mock:with-attributes&gt;
            &lt;mock:then-return payload="#[]"&gt;
                &lt;mock:invocation-properties&gt;
                    &lt;mock:invocation-property key="my_variable" value="#['var_value_1']"/&gt;
                &lt;/mock:invocation-properties&gt;
            &lt;/mock:then-return&gt;
        &lt;/mock:when&gt;
        &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
        &lt;munit:assert-payload-equals message="oops, wrong payload!" expectedValue="#['response_payload_1']" doc:name="Assert Payload"/&gt;
    &lt;/munit:test&gt;

    &lt;munit:test name="doc-test-exampleFlow-unit-Test_2" description="Unit Test case asserting scenario 2"&gt;
        &lt;mock:when messageProcessor="mule:set-payload" doc:name="Mock"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#['Set Original Payload']" name="doc:name"/&gt;
            &lt;/mock:with-attributes&gt;
            &lt;mock:then-return payload="#[]"/&gt;
        &lt;/mock:when&gt;
        &lt;mock:when messageProcessor="mule:flow" doc:name="Mock"&gt;
            &lt;mock:with-attributes&gt;
                &lt;mock:with-attribute whereValue="#['exampleFlow2']" name="name"/&gt;
            &lt;/mock:with-attributes&gt;
            &lt;mock:then-return payload="#[]"&gt;
                &lt;mock:invocation-properties&gt;
                    &lt;mock:invocation-property key="my_variable" value="#['var_value_2']"/&gt;
                &lt;/mock:invocation-properties&gt;
            &lt;/mock:then-return&gt;
        &lt;/mock:when&gt;
        &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
        &lt;munit:assert-payload-equals message="oops, wrong payload!" expectedValue="#['response_payload_2']" doc:name="Assert Payload"/&gt;
    &lt;/munit:test&gt;

    &lt;!-- exampleFlow Functional Tests --&gt;
    &lt;munit:test name="doc-test-exampleFlow-functionalTest_1" description="Funtional Test case asserting scenario 1"&gt;
        &lt;munit:set payload="#['']" doc:name="Set Message url_key:payload_1"&gt;
            &lt;munit:inbound-properties&gt;
                &lt;munit:inbound-property key="http.query.params" value="#[['url_key':'payload_1']]"/&gt;
            &lt;/munit:inbound-properties&gt;
        &lt;/munit:set&gt;
        &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
        &lt;munit:assert-payload-equals message="oops, wrong payload!" expectedValue="#['response_payload_1']" doc:name="Assert Payload"/&gt;
    &lt;/munit:test&gt;

    &lt;munit:test name="doc-test-exampleFlow-functionalTest_2" description="Funtional Test case asserting scenario 2"&gt;
        &lt;munit:set payload="#['']" doc:name="Set Message url_key:payload_1"&gt;
            &lt;munit:inbound-properties&gt;
                &lt;munit:inbound-property key="http.query.params" value="#[['url_key':'payload_2']]"/&gt;
            &lt;/munit:inbound-properties&gt;
        &lt;/munit:set&gt;
        &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
        &lt;munit:assert-payload-equals message="oops, wrong payload!" expectedValue="#['response_payload_2']" doc:name="Assert Payload"/&gt;
    &lt;/munit:test&gt;

&lt;/mule&gt;

In the sections below we break down and analyze the Test Suite file. When performing unit tests, it’s always better to take a ground-up approach, first testing the building blocks of the code.

Always test the building blocks of your code first, then test the more complex code.

We start by testing exampleFlow2.

Ideally, you should test each and every flow and sub-flow in your application, in order to validate that each one of them behaves as expected. Since we’ve complicated things a little in order to show you more scenarios, we skip testing the sub-flows exampleSub_Flow1 and exampleSub_Flow2). In a real application, we should start by testing those two flows.

Ideally, you should test each and every flow and sub-flow in your application.

MUnit Test Suite "Musts"

Each MUnit test file must contain the following three beans:

  • MUnit config

  • The import section

These are shown in the snippet below:

must-global-elements must-import-bean


    
             
          
1
2
3
4
5
6
7
&lt;!-- MUnit config --&gt;
&lt;munit:config name="munit" doc:name="Munit configuration"/&gt;

&lt;!-- The import section --&gt;
&lt;spring:beans&gt;
    &lt;spring:import resource="classpath:demo.xml"/&gt;
&lt;/spring:beans&gt;

In the import section, we define the files needed for this test to run. This section usually includes the file containing the flows we want to test, and additional files required for the first file to work.

MUnit Test Suite files cannot run without MUnit config.

Testing: exampleFlow2

We start by analyzing the simplest flow in the application, exampleFlow2.

This flow contains a choice router, which provides two different paths that the code can follow. Here we test both of them.

In a real application, always test all possible paths.

exampleFlow2


    
             
          
1
2
3
4
5
6
7
8
9
10
&lt;flow name="exampleFlow2"&gt;
  &lt;choice doc:name="Choice"&gt;
    &lt;when expression="#['payload_1'.equals(payload)]"&gt;
      &lt;flow-ref name="exampleSub_Flow1" doc:name="exampleSub_Flow1"/&gt;
    &lt;/when&gt;
    &lt;otherwise&gt;
      &lt;flow-ref name="exampleSub_Flow2" doc:name="exampleSub_Flow2"/&gt;
    &lt;/otherwise&gt;
  &lt;/choice&gt;
&lt;/flow&gt;

We start with the first path.

break-first-test

  1. Define input message to be sent to the production flow exampleFlow2.

  2. Make call to production code.

  3. Validate success of the test by using a verification.


    
             
          
1
2
3
4
5
6
7
8
9
10
11
&lt;munit:test name="doc-test-exampleFlow2Test1" description="Validate calls to sub flows are being done properly "&gt;
  &lt;munit:set payload="#['payload_1']" doc:name="Set Message payload == payload_1"/&gt;                         <i class="conum" data-value="1"></i><b>(1)</b>

  &lt;flow-ref name="exampleFlow2" doc:name="Flow-ref to exampleFlow2"/&gt;                                           <i class="conum" data-value="2"></i><b>(2)</b>

  &lt;mock:verify-call messageProcessor="mule:sub-flow" doc:name="Verify Call" times="1"&gt;    <i class="conum" data-value="3"></i><b>(3)</b>
    &lt;mock:with-attributes&gt;
      &lt;mock:with-attribute whereValue="#[matchContains('exampleSub_Flow1')]" name="name"/&gt;
    &lt;/mock:with-attributes&gt;
  &lt;/mock:verify-call&gt;
&lt;/munit:test&gt;

<1> Define input message to be sent to the production flow exampleFlow2. <2> Make call to production code. <3> Validate success of the test by using a verification.

This test looks fairly simple, but it has a few points to highlight.

The first thing we do is to create an input message. This is a very common scenario; you probably have to create input messages for the flows that you test. In this example it was only necessary to define a payload, but further down in this tutorial we see how to create more complex messages.

For the purposes of this test, we can be confident that the code works properly by simply ensuring that the correct message processor was called. We could also have added an assertion over the variables that were supposed to be set.

Finally, notice that the message processor to call is a flow-ref. In MUnit, you don’t mock or verify flow-ref, but the flow or sub-flow that would be invoked by flow-ref. If you check closely, you see that we are not verifying the flow-ref message processor, but running a verification over the mule:sub-flow message processor.

In MUnit you don’t mock or verify flow-ref, you mock or verify the flow and sub-flow.
Using flow-ref is the most common way to trigger your production code. Even if the flow you’re testing is a not a private flow, the usual way to invoke it is by using flow-ref, rather than calling the flow’s inbound endpoints such as HTTP, VM, JSM, etc.

Another thing to notice is how we are defining the name of the sub-flow. Instead of just typing the name of the sub-flow, we are using the MUnit matcher matchContains:


          
       
1
#[matchContains('exampleSub_Flow1')]

This is not needed when verifying or mocking flows, only for sub-flows.

When mocking or verifying a sub-flow and using the name attribute, always use the MUnit matcher matchContains.

So far we have only tested one branch of exampleFlow2; we need to test the other one. To do that, we add another test.

break-second-test


    
             
          
1
2
3
4
5
6
7
8
9
10
11
&lt;munit:test name="doc-test-exampleFlow2Test2" description="Validate calls to sub flows are being done properly "&gt;
  &lt;munit:set payload="#['payload_2']" doc:name="Set Message payload == payload_2"/&gt;

  &lt;flow-ref name="exampleFlow2" doc:name="Flow-ref to exampleFlow2"/&gt;

  &lt;mock:verify-call messageProcessor="mule:sub-flow" doc:name="Verify Call" times="1"&gt;
    &lt;mock:with-attributes&gt;
      &lt;mock:with-attribute whereValue="#[matchContains('exampleSub_Flow2')]" name="name"/&gt;
    &lt;/mock:with-attributes&gt;
  &lt;/mock:verify-call&gt;
&lt;/munit:test&gt;

This test is very similar to the first, except for one crucial change:

break-set-payload-2


    
             
          
1
&lt;munit:set payload="#['payload_2']" doc:name="Set Message payload == payload_2"/&gt;

When we define the message to send to the production code, we are changing the payload in order to engage the other branch of the code. This may look obvious to experienced developers, but it is a common mistake.

If your production code takes different actions based on different values of the payload or on the contents of a variable, you should probably design more that one test for that production flow.

Testing: exampleFlow

The most complex flow in this application is the last flow, exampleFlow.

This flow contains a choice router, which provides two different paths that the code can follow. As in the previous case, we test both of them.

break-example-flow


    
             
          
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;flow name="exampleFlow"&gt;
  &lt;http:listener config-ref="HTTP_Listener_Configuration" path="/" allowedMethods="GET" doc:name="HTTP"/&gt;
  &lt;set-payload value="#[message.inboundProperties['http.query.params']['url_key']]" doc:name="Set Original Payload"/&gt;

  &lt;flow-ref name="exampleFlow2" doc:name="exampleFlow2"/&gt;

  &lt;choice doc:name="Choice"&gt;
    &lt;when expression="#[flowVars['my_variable'].equals('var_value_1')]"&gt;
      &lt;set-payload value="#['response_payload_1']" doc:name="Set Response Payload"/&gt;
    &lt;/when&gt;
    &lt;otherwise&gt;
      &lt;set-payload value="#['response_payload_2']" doc:name="Set Response Payload"/&gt;
    &lt;/otherwise&gt;
    &lt;/choice&gt;
&lt;/flow&gt;

This flow contains an http-listener, but in order to show you different scenarios we are not going to call it. Since we are not calling the HTTP listener, we need to take a few other actions for this test to work properly.

As with our first flow, here we start with the first path contained in the flow.

break-choice-test

  1. Define mock for the set-payload message processor in exampleFlow.

  2. Define mock for the call to exampleFlow2.

  3. Make call to production code.

  4. Validate success of the test by asserting the returned payload.


    
             
          
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;munit:test name="doc-test-exampleFlow-unit-Test_1"
  description="Unit Test case asserting scenario 1"&gt;

  &lt;mock:when messageProcessor="mule:set-payload" doc:name="Mock"&gt; <i class="conum" data-value="1"></i><b>(1)</b>
    &lt;mock:with-attributes&gt;
      &lt;mock:with-attribute whereValue="#['Set Original Payload']" name="doc:name"/&gt;
    &lt;/mock:with-attributes&gt;
    &lt;mock:then-return payload="#[]"/&gt;
  &lt;/mock:when&gt;

  &lt;mock:when messageProcessor="mule:flow" doc:name="Mock"&gt; <i class="conum" data-value="2"></i><b>(2)</b>
    &lt;mock:with-attributes&gt;
      &lt;mock:with-attribute whereValue="#['exampleFlow2']" name="name"/&gt;
      &lt;/mock:with-attributes&gt;
    &lt;mock:then-return payload="#[]"&gt;
      &lt;mock:invocation-properties&gt;
        &lt;mock:invocation-property key="my_variable" value="#['var_value_1']"/&gt;
      &lt;/mock:invocation-properties&gt;
    &lt;/mock:then-return&gt;
  &lt;/mock:when&gt;

  &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;                                <i class="conum" data-value="3"></i><b>(3)</b>

  &lt;munit:assert-payload-equals message="oops, wrong payload!" expectedValue="#['response_payload_1']" doc:name="Assert Payload"/&gt; <i class="conum" data-value="4"></i><b>(4)</b>
&lt;/munit:test&gt;

<1> Define mock for the set-payload message processor in exampleFlow. <2> Define mock for the call to exampleFlow2. <3> Make call to production code. <4> Validate success of the test by asserting the returned payload.

The first thing to notice in this test is that we are defining mocks. Mocks are what allow you to isolate your flow, distinguishing it from third-party systems and any other flows in your application.

The first mock we define is for the set-payload message processor. We do this because this message processor expects a certain set of inbound variables, but we won’t send them in this test — hence, for the code to succeed we need to mock the behavior of the set-payload message processor.

break-first-mock


    
             
          
1
2
3
4
5
6
&lt;mock:when messageProcessor="mule:set-payload" doc:name="Mock"&gt;
  &lt;mock:with-attributes&gt;
    &lt;mock:with-attribute whereValue="#['Set Original Payload']" name="doc:name"/&gt;
  &lt;/mock:with-attributes&gt;
  &lt;mock:then-return payload="#[]"/&gt;
&lt;/mock:when&gt;

Notice that we are not actually returning a payload. The payload in the set-payload message processor is needed by exampleFlow2. In this unit test, we trust exampleFlow2 to work as expected, and mock it as well.

When doing unit tests, you isolate your flow from third-party systems and other flows and trust they work as expected. In turn, you must test each third-party system or flow with its own, specific test.

break-second-mock


    
             
          
1
2
3
4
5
6
7
8
9
10
&lt;mock:when messageProcessor="mule:flow" doc:name="Mock"&gt;
  &lt;mock:with-attributes&gt;
    &lt;mock:with-attribute whereValue="#['exampleFlow2']" name="name"/&gt;
    &lt;/mock:with-attributes&gt;
  &lt;mock:then-return payload="#[]"&gt;
    &lt;mock:invocation-properties&gt;
      &lt;mock:invocation-property key="my_variable" value="#['var_value_1']"/&gt;
    &lt;/mock:invocation-properties&gt;
  &lt;/mock:then-return&gt;
&lt;/mock:when&gt;

If you’ve been reading this tutorial from the beginning, you already know that in MUnit you do not mock flow-ref message processors, you mock the flows that would be called by them (see above). That’s what we’re doing here, mocking exampleFlow2 which was called from exampleFlow.

The purpose of exampleFlow2 was to set the value of the invocation variable my_var. If you look closely at this mock, you see that we are telling the mocked flow to return a message that contains an invocation variable named my_var with a value of var_value_1. This is what should happen in the first test scenario.

Now that our two mocks are in place, we run the production code:

break-flow-ref

&lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;

The only thing that remains to be done for this test is to define its success criteria. For the purposes of this example, we determine whether it was successful based on the payload returned by the flow.

break-assert-payload


    
             
          
1
2
3
&lt;munit:assert-payload-equals message="oops, wrong payload!"
 expectedValue="#['response_payload_1']"
 doc:name="Assert Payload"/&gt; <i class="conum" data-value="4"></i><b>(4)</b>

As you can see, we are validating that the payload returned is equal to that set by the first branch of the choice in the production code, that is, response_payload_1.

Now we test the other branch.

break-mock-2
break-payload-2

  1. First difference with first branch: When mocking exampleFlow2, we’re telling it to return a variable with a different value: var_value_2. This should trigger the second branch of the choice.

  2. Second difference with first branch: We are also changing the assertion, because the mock before the returned payload has changed. Hence the need to modify our success criteria.


    
             
          
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;munit:test name="doc-test-exampleFlow-unit-Test_2"
  description="Unit Test case asserting scenario 2"&gt;
    &lt;mock:when messageProcessor="mule:set-payload" doc:name="Mock"&gt;
        &lt;mock:with-attributes&gt;
            &lt;mock:with-attribute whereValue="#['Set Original Payload']" name="doc:name"/&gt;
        &lt;/mock:with-attributes&gt;
        &lt;mock:then-return payload="#[]"/&gt;
    &lt;/mock:when&gt;

    &lt;mock:when messageProcessor="mule:flow" doc:name="Mock"&gt;
        &lt;mock:with-attributes&gt;
            &lt;mock:with-attribute whereValue="#['exampleFlow2']" name="name"/&gt;
        &lt;/mock:with-attributes&gt;
        &lt;mock:then-return payload="#[]"&gt;
            &lt;mock:invocation-properties&gt;
                &lt;mock:invocation-property key="my_variable"
                  value="#['var_value_2']"/&gt; <i class="conum" data-value="1"></i><b>(1)</b>
            &lt;/mock:invocation-properties&gt;
        &lt;/mock:then-return&gt;
    &lt;/mock:when&gt;

    &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
    &lt;munit:assert-payload-equals message="oops, wrong payload!"
                expectedValue="#['response_payload_2']" doc:name="Assert Payload"/&gt; <i class="conum" data-value="2"></i><b>(2)</b>
&lt;/munit:test&gt;
  1. First difference with first branch: When mocking exampleFlow2, we’re telling it to return a variable with a different value: var_value_2. This should trigger the second branch of the choice

  2. Second difference with first branch: We are also changing the assertion, because the mock before the returned payload has changed. Hence the need to modify our success criteria

Functional Testing

All of the tests explained so far were unit tests, which try to isolate each flow as much as possible from the other flows.

You may also want to do a functional test, that is, an end-to-end test. In our example, this means that we are not going to mock any message processor. To implement a test in this way, we need to correctly define the message that we send to the production code.

In previous tests, we mocked the first message processor of exampleFlow because it needed the message to contain specific values. Since we are not mocking anything now, we have to create that message.

break-first-functional-test


    
             
          
1
2
3
4
5
6
7
8
9
10
11
12
&lt;munit:test name="doc-test-exampleFlow-functionalTest_1"
  description="Functional Test case asserting scenario 1"&gt;
    &lt;munit:set payload="#['']" doc:name="Set Message url_key:payload_1"&gt;
        &lt;munit:inbound-properties&gt;
            &lt;munit:inbound-property key="http.query.params"
              value="#[['url_key':'payload_1']]"/&gt;
        &lt;/munit:inbound-properties&gt;
    &lt;/munit:set&gt;
    &lt;flow-ref name="exampleFlow" doc:name="Flow-ref to exampleFlow"/&gt;
    &lt;munit:assert-payload-equals message="oops, wrong payload!"
      expectedValue="#['response_payload_1']" doc:name="Assert Payload"/&gt;
&lt;/munit:test&gt;

This test is very similar to the others for exampleFlow, without the mocks.

Let’s check again the implementation of exampleFlow, specifically the set-payload:

set-original-payload


    
             
          
1
2
&lt;set-payload value="#[message.inboundProperties['http.query.params']['url_key']]"
  doc:name="Set Original Payload"/&gt;

The set-payload message processor is expecting the message to have a inbound property named http.query.params, which should be a map. The map should contain the key url_key.

The code below shows how to create such a message:

break-functional-set-payload


    
             
          
1
2
3
4
5
6
&lt;munit:set payload="#['']" doc:name="Set Message url_key:payload_1"&gt;
    &lt;munit:inbound-properties&gt;
        &lt;munit:inbound-property key="http.query.params"
          value="#[['url_key':'payload_1']]"/&gt;
    &lt;/munit:inbound-properties&gt;
&lt;/munit:set&gt;

Conclusion

In this tutorial, we’ve seen:

  • How to create MUnit tests

  • How to create Mule messages

  • How to create mocks

  • How to run verifications and assertions

In short, we’ve covered a great deal of the MUnit features.

As you code, your tests may become as large and complex as your production code. The tools provided by MUnit help you create great tests while maintaining the quality of your code.