11.3 MUnit 2.x Testing: Unit Tests, Mocking Connectors & Assertions

Key Takeaways

  • MUnit 2.x is the native unit and integration testing framework for Mule 4, organized into XML test suites under `src/test/munit` and executed via Anypoint Studio or the `munit-maven-plugin` in CI/CD pipelines.
  • Every `<munit:test>` follows a three-phase lifecycle: `<munit:behavior>` (preconditions, mocks, spies), `<munit:execution>` (event setup and flow invocation), and `<munit:validation>` (assertions and call verifications).
  • The `<munit-tools:mock-when>` processor intercepts calls to external connectors (such as HTTP Request or Database) and substitutes simulated payloads, attributes, or simulated errors without invoking backend systems.
  • The `<munit-tools:verify-call>` processor validates the exact execution frequency of a processor (e.g., verifying a database insert was called `times='1'` or an alert processor was called `times='0'`).
  • MUnit coverage reports calculate flow, file, and application processor coverage, enabling automated build failure enforcement via Maven quality gates when thresholds are unmet.
Last updated: August 2026

MUnit 2.x Testing: Unit Tests, Mocking Connectors & Assertions

MUnit 2.x is the native testing framework designed specifically for Mule 4 applications. Fully integrated with Anypoint Studio and Apache Maven, MUnit allows developers to write automated unit and integration tests for flows, sub-flows, and error handlers. By mocking external endpoints, spying on intermediate message states, and verifying execution paths, MUnit ensures high software quality and prevents regressions in CI/CD automation.


1. MUnit 2.x Architecture & Project Structure

MUnit tests are authored as standard Mule XML configuration files located in the project's src/test/munit directory. Test artifacts, sample payloads, and mock responses are stored in src/test/resources.

+-------------------------------------------------------------------------+
|                        MUNIT PROJECT STRUCTURE                          |
|                                                                         |
|   my-mule-app/                                                          |
|   ├── src/main/mule/               <-- Application Flow XML Files       |
|   │   ├── orders-api.xml                                                |
|   │   └── global-configs.xml                                            |
|   ├── src/main/resources/          <-- Application Properties & Log4j 2 |
|   ├── src/test/munit/              <-- MUnit Test Suite XML Files       |
|   │   ├── orders-api-test-suite.xml                                     |
|   │   └── error-handling-test-suite.xml                                 |
|   ├── src/test/resources/          <-- Mock Payloads, Samples, Schemas  |
|   │   ├── sample-order-request.json                                     |
|   │   └── mock-db-customer-response.json                                |
|   └── pom.xml                      <-- munit-maven-plugin & Dependencies|
+-------------------------------------------------------------------------+

Lifecycle Scopes in an MUnit Test Suite:

  • <munit:before-suite> / <munit:after-suite>: Executes once before/after all test cases in the suite file (ideal for spinning up embedded test brokers or database fixtures).
  • <munit:before-test> / <munit:after-test>: Executes before/after each individual <munit:test> case in the file (ideal for resetting variables or cleaning temporary state).
  • <munit:test>: Defines an individual automated test case.

2. The Three-Phase Test Lifecycle (<munit:test>)

Every MUnit test case is organized into three distinct, sequential execution sections:

+-------------------------------------------------------------------------+
|                       MUNIT 3-PHASE TEST LIFECYCLE                      |
|                                                                         |
|   +-----------------------------------------------------------------+   |
|   | 1. BEHAVIOR (<munit:behavior>)                                  |   |
|   |    - Configure Mock processors (<munit-tools:mock-when>)        |   |
|   |    - Configure Spies (<munit-tools:spy>)                        |   |
|   |    - Define preconditions and stubbed return values             |   |
|   +-----------------------------------------------------------------+   |
|                                    |                                    |
|                                    v                                    |
|   +-----------------------------------------------------------------+   |
|   | 2. EXECUTION (<munit:execution>)                                |   |
|   |    - Initialize Event (<munit:set-event>)                       |   |
|   |    - Invoke target flow under test (<flow-ref>)                 |   |
|   +-----------------------------------------------------------------+   |
|                                    |                                    |
|                                    v                                    |
|   +-----------------------------------------------------------------+   |
|   | 3. VALIDATION (<munit:validation>)                              |   |
|   |    - Assert payload & variables (<munit-tools:assert-that>)     |   |
|   |    - Verify execution counts (<munit-tools:verify-call>)        |   |
|   +-----------------------------------------------------------------+   |
+-------------------------------------------------------------------------+

Comprehensive Example of an MUnit Test Case:

<munit:test name='createOrder-validPayload-returns201' 
            description='Verify that valid order submission creates record and returns HTTP 201' 
            doc:id='test-001'>
    
    <!-- PHASE 1: BEHAVIOR -->
    <munit:behavior>
        <!-- Mock the Database Insert Processor -->
        <munit-tools:mock-when processor='db:insert'>
            <munit-tools:with-attributes>
                <munit-tools:with-attribute attributeName='doc:name' whereValue='Insert Order Record' />
            </munit-tools:with-attributes>
            <munit-tools:then-return>
                <munit-tools:payload value='#[{ affectedRows: 1 }]' mediaType='application/java' />
            </munit-tools:then-return>
        </munit-tools:mock-when>
    </munit:behavior>
    
    <!-- PHASE 2: EXECUTION -->
    <munit:execution>
        <!-- Construct Inbound Mule Event -->
        <munit:set-event doc:name='Set Initial Request Event'>
            <munit:payload value="#[MunitTools::getResourceAsString(&apos;sample-order-request.json&apos;)]" 
                           mediaType='application/json' />
            <munit:attributes value="#[{ headers: { &apos;client_id&apos;: &apos;test-client-123&apos; } }]" />
        </munit:set-event>
        
        <!-- Trigger Flow Under Test -->
        <flow-ref name='post:\orders:api-config' />
    </munit:execution>
    
    <!-- PHASE 3: VALIDATION -->
    <munit:validation>
        <!-- Assert Resulting Payload Status -->
        <munit-tools:assert-that expression='#[payload.status]' 
                                is="#[MunitTools::equalTo(&apos;CREATED&apos;)]" 
                                message='Expected order status to be CREATED' />
        
        <!-- Assert HTTP Status Code Attribute -->
        <munit-tools:assert-that expression='#[vars.httpStatus]' 
                                is='#[MunitTools::equalTo(201)]' 
                                message='Expected HTTP Status code 201' />
        
        <!-- Verify the DB Insert was called exactly once -->
        <munit-tools:verify-call processor='db:insert' times='1'>
            <munit-tools:with-attributes>
                <munit-tools:with-attribute attributeName='doc:name' whereValue='Insert Order Record' />
            </munit-tools:with-attributes>
        </munit-tools:verify-call>
    </munit:validation>
</munit:test>

3. Core Mocking, Spying & Verification Processors

ProcessorPlacement ScopePrimary PurposeKey Attributes / Child Elements
<munit-tools:mock-when><munit:behavior>Intercepts a message processor and returns stubbed data or errors without invoking external backends.processor, <with-attributes>, <then-return> (payload, attributes, variables, errorType)
<munit-tools:spy><munit:behavior>Inspects the Mule event immediately before and immediately after a processor executes.processor, <before-call> (assertions), <after-call> (assertions)
<munit-tools:verify-call><munit:validation>Verifies how many times a specific processor was executed during the test run.processor, times='N', atLeast='N', atMost='N'
<munit:set-event><munit:execution>Constructs a simulated inbound Mule event with custom payload, attributes, and variables.<payload>, <attributes>, <variables>
<munit-tools:assert-that><munit:validation>Evaluates a DataWeave expression against a Hamcrest matcher condition.expression, is, message
<munit-tools:assert-equals><munit:validation>Compares actual value against expected value for exact equality.actual, expected, message

4. Mocking Exceptions & Testing Error Handlers

MUnit allows testing error handling flows without intentionally sabotaging external networks. By configuring <munit-tools:mock-when> to throw an error, developers simulate any Mule error type (e.g., HTTP:CONNECTIVITY, DB:BAD_SQL_SYNTAX, APP:INVALID_ACCOUNT).

<!-- Testing Error Handling with Mocked Exception -->
<munit:test name='postOrder-dbDown-handlesConnectivityError' doc:id='test-002'>
    <munit:behavior>
        <!-- Mock DB to throw HTTP:CONNECTIVITY error -->
        <munit-tools:mock-when processor='db:insert'>
            <munit-tools:then-return>
                <munit-tools:error errorTypeId='DB:CONNECTIVITY' />
            </munit-tools:then-return>
        </munit-tools:mock-when>
    </munit:behavior>
    
    <munit:execution>
        <munit:set-event>
            <munit:payload value="#[{ orderId: &apos;ORD-123&apos;, total: 99.00 }]" mediaType='application/json' />
        </munit:set-event>
        <flow-ref name='post:\orders:api-config' />
    </munit:execution>
    
    <munit:validation>
        <!-- Verify on-error-continue transformed payload to graceful 503 error message -->
        <munit-tools:assert-equals actual='#[vars.httpStatus]' expected='#[503]' />
        <munit-tools:assert-that expression='#[payload.error.code]' 
                                is="#[MunitTools::equalTo(&apos;SERVICE_UNAVAILABLE&apos;)]" />
    </munit:validation>
</munit:test>

Handling on-error-propagate with expectedErrorType:

If the target flow uses on-error-propagate, the error is re-thrown by the flow reference. In MUnit, unhandled re-thrown errors will fail the test unless the test explicitly declares expectedErrorType:

<munit:test name='validateOrder-invalidPayload-throwsCustomError' 
            expectedErrorType='ORDER:INVALID_PAYLOAD'>
    <munit:execution>
        <munit:set-event>
            <munit:payload value='#[{ badData: true }]' mediaType='application/json' />
        </munit:set-event>
        <flow-ref name='validate-order-flow' />
    </munit:execution>
</munit:test>

5. Hamcrest Matchers Reference (MunitTools::)

MUnit includes built-in Hamcrest matchers accessed via the MunitTools:: DataWeave module in <munit-tools:assert-that>:

Matcher FunctionExample ExpressionDescription
equalTo(value)is="#[MunitTools::equalTo(&apos;ACTIVE&apos;)]"Asserts exact structural and value equality.
not(matcher)is='#[MunitTools::not(MunitTools::equalTo(0))]'Inverts the specified matcher logic.
nullValue()is='#[MunitTools::nullValue()]'Asserts that the expression evaluates to null.
notNullValue()is='#[MunitTools::notNullValue()]'Asserts that the expression is not null.
hasSize(number)is='#[MunitTools::hasSize(5)]'Asserts array or collection element count.
hasItem(value)is="#[MunitTools::hasItem(&apos;ADMIN&apos;)]"Asserts collection contains the specified item.
everyItem(matcher)is='#[MunitTools::everyItem(MunitTools::notNullValue())]'Asserts every item in collection matches condition.
containsString(str)is="#[MunitTools::containsString(&apos;Success&apos;)]"Asserts target string contains substring.
startsWith(str)is="#[MunitTools::startsWith(&apos;ORD-&apos;)]"Asserts string begins with target prefix.
isEmpty()is='#[MunitTools::isEmpty()]'Asserts collection, string, or map is empty.

6. MUnit Coverage Reports & Maven Quality Gates

Automated CI/CD pipelines use the munit-maven-plugin in pom.xml to execute test suites and generate code coverage reports.

+-------------------------------------------------------------------------+
|                         MUNIT COVERAGE METRICS                          |
|                                                                         |
|   1. APPLICATION COVERAGE:                                              |
|      Percentage of all message processors executed across the entire    |
|      application during test suite execution.                           |
|                                                                         |
|   2. RESOURCE / FILE COVERAGE:                                          |
|      Percentage of message processors executed within a specific XML    |
|      configuration file (e.g., orders-api.xml).                         |
|                                                                         |
|   3. FLOW COVERAGE:                                                     |
|      Percentage of message processors executed within an individual     |
|      Mule flow or sub-flow.                                             |
+-------------------------------------------------------------------------+

Configuring Coverage Thresholds in pom.xml:

<plugin>
    <groupId>com.mulesoft.munit.tools</groupId>
    <artifactId>munit-maven-plugin</artifactId>
    <version>2.3.14</version>
    <executions>
        <execution>
            <id>test</id>
            <phase>test</phase>
            <goals>
                <goal>test</goal>
                <goal>coverage-report</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <coverage>
            <runCoverage>true</runCoverage>
            <failBuild>true</failBuild>
            <requiredApplicationCoverage>80</requiredApplicationCoverage>
            <requiredResourceCoverage>75</requiredResourceCoverage>
            <requiredFlowCoverage>70</requiredFlowCoverage>
            <formats>
                <format>html</format>
                <format>json</format>
                <format>console</format>
            </formats>
        </coverage>
    </configuration>
</plugin>
  • If <failBuild>true</failBuild> is set and the calculated coverage falls below any configured threshold (requiredApplicationCoverage, requiredResourceCoverage, or requiredFlowCoverage), the Maven build command (mvn clean test) terminates with a build failure, preventing deployment of untested code.

7. Exam Watch: Core MUnit Scenarios

[!IMPORTANT] Placement of <munit-tools:mock-when> On the exam, <munit-tools:mock-when> and <munit-tools:spy> processors must always be placed inside the <munit:behavior> section. Placing mocks inside <munit:execution> or <munit:validation> is an invalid configuration.

[!WARNING] Testing on-error-continue vs on-error-propagate

  • If a flow handles an exception with on-error-continue, the flow completes successfully and returns a transformed message. Do not set expectedErrorType on the <munit:test>.
  • If a flow handles an exception with on-error-propagate, the error is re-thrown. The <munit:test> must declare expectedErrorType='NAMESPACE:IDENTIFIER' to pass.

[!TIP] Verifying Conditional Branches with times='0' Use <munit-tools:verify-call processor='smtp:send' times='0' /> to verify that an alert email was never sent when testing a valid non-error execution path.

Test Your Knowledge

A Mule flow queries a backend customer database using a Database Select processor (processor='db:select') and transforms the result into JSON. A developer is writing an MUnit test to validate the transformation logic without connecting to the physical database. In which section of the <munit:test> should the developer place the <munit-tools:mock-when> component, and what must it return?

A
B
C
D
Test Your Knowledge

An order processing flow contains a Choice router that evaluates customer credit. If credit is approved, an external JMS Publish processor sends the order to a warehouse queue; if rejected, an email notification processor (processor='email:send') is invoked. When authoring an MUnit test for the 'credit rejected' scenario, how should the developer verify that no JMS message was published?

A
B
C
D
Test Your Knowledge

A developer needs to test a Mule flow that begins with an HTTP Listener and expects an inbound URI parameter orderId='ORD-5542' and an HTTP header authorization='Bearer token123'. What MUnit component should be placed inside <munit:execution> prior to the <flow-ref> to construct these inbound parameters?

A
B
C
D
Test Your Knowledge

A company enforces an automated CI/CD code quality policy requiring 80% application code coverage. During a Maven build (mvn clean test), the MUnit execution logs show that application processor coverage reached 74%, and the Maven build terminates with a BUILD FAILURE error. Which configuration in pom.xml caused the build to fail?

A
B
C
D