MuleSoft Writing MUnit Test Cases
Writing good MUnit test cases requires thinking about both the happy path (valid input, expected success) and the sad paths (invalid input, missing fields, external failures). This topic walks through writing complete, practical test cases for a real order creation flow covering multiple scenarios.
The Flow Being Tested
The target flow is createOrderFlow. It receives a POST request, validates required fields, saves to a database, calls a shipping API, and returns a 201 response with the new order ID.
Flow Under Test
createOrderFlow:
[HTTP Listener: POST /orders]
│
▼
[Choice: payload.product == null?]
YES → [Set Payload: error] → [Set Var: httpStatus=400]
NO ↓
▼
[Database: INSERT order]
│
▼
[Set Variable: orderId = payload[0].GENERATED_KEY]
│
▼
[HTTP Request: POST shipping-api/shipments]
│
▼
[Set Payload: {orderId, trackingNum}]
[Set Var: httpStatus=201]
Test Case 1: Happy Path
This test verifies the flow succeeds when all required fields are present.
<munit:test name="createOrder_validInput_returns201"
description="Valid order should be created and return 201">
<!-- GIVEN: mock DB and shipping API -->
<munit:behavior>
<!-- Mock database insert -->
<munit-tools:mock-when processor="db:insert">
<munit-tools:with-attributes>
<munit-tools:with-attribute attributeName="doc:name"
whereValue="Save Order to DB"/>
</munit-tools:with-attributes>
<munit-tools:then-return>
<munit-tools:payload
value='[{"GENERATED_KEY": 5042}]'
mediaType="application/java"/>
</munit-tools:then-return>
</munit-tools:mock-when>
<!-- Mock shipping API call -->
<munit-tools:mock-when processor="http:request">
<munit-tools:with-attributes>
<munit-tools:with-attribute attributeName="doc:name"
whereValue="Call Shipping API"/>
</munit-tools:with-attributes>
<munit-tools:then-return>
<munit-tools:payload
value='{"trackingNumber": "FX9988776655"}'
mediaType="application/json"/>
<munit-tools:attributes
value='#[{"statusCode": 200}]'
mediaType="application/java"/>
</munit-tools:then-return>
</munit-tools:mock-when>
</munit:behavior>
<!-- WHEN: send a valid order -->
<munit:execution>
<munit:set-event>
<munit:payload
value='{"product": "Laptop", "qty": 1, "price": 999.99}'
mediaType="application/json"/>
</munit:set-event>
<flow-ref name="createOrderFlow"/>
</munit:execution>
<!-- THEN: verify result -->
<munit:validation>
<munit-tools:assert-equals
actual="#[output application/java --- vars.httpStatus]"
expected="#[201]"
message="HTTP status should be 201 Created"/>
<munit-tools:assert-equals
actual="#[output application/java --- payload.orderId]"
expected="#[5042]"
message="Order ID should match DB generated key"/>
<munit-tools:assert-equals
actual="#[output application/java --- payload.trackingNumber]"
expected="FX9988776655"
message="Tracking number should come from shipping API"/>
</munit:validation>
</munit:test>
Test Case 2: Missing Required Field
This test verifies the flow returns 400 when the product field is null.
<munit:test name="createOrder_missingProduct_returns400"
description="Order without product field should return 400">
<!-- GIVEN: no mocks needed — DB and shipping API never called -->
<munit:behavior/>
<!-- WHEN: send payload without required field -->
<munit:execution>
<munit:set-event>
<munit:payload
value='{"qty": 1, "price": 999.99}'
mediaType="application/json"/>
<!-- product field deliberately missing -->
</munit:set-event>
<flow-ref name="createOrderFlow"/>
</munit:execution>
<!-- THEN: verify 400 error -->
<munit:validation>
<munit-tools:assert-equals
actual="#[output application/java --- vars.httpStatus]"
expected="#[400]"
message="Missing product should cause 400 Bad Request"/>
<munit-tools:assert-not-null
value="#[payload.error]"
message="Error message should be present in response"/>
</munit:validation>
</munit:test>
Test Case 3: Database Failure
This test verifies the flow handles a database error gracefully and returns 503.
<munit:test name="createOrder_dbOffline_returns503"
description="DB failure should return 503 Service Unavailable">
<munit:behavior>
<!-- Mock DB insert to throw a connectivity error -->
<munit-tools:mock-when processor="db:insert">
<munit-tools:with-attributes>
<munit-tools:with-attribute attributeName="doc:name"
whereValue="Save Order to DB"/>
</munit-tools:with-attributes>
<munit-tools:then-throw>
<munit-tools:error typeId="DB:CONNECTIVITY"
description="Cannot connect to database"/>
</munit-tools:then-throw>
</munit-tools:mock-when>
</munit:behavior>
<munit:execution>
<munit:set-event>
<munit:payload
value='{"product": "Pen", "qty": 5, "price": 7.50}'
mediaType="application/json"/>
</munit:set-event>
<flow-ref name="createOrderFlow"/>
</munit:execution>
<munit:validation>
<munit-tools:assert-equals
actual="#[output application/java --- vars.httpStatus]"
expected="#[503]"
message="DB failure should return 503"/>
<munit-tools:assert-equals
actual="#[output application/java --- payload.error]"
expected="Service temporarily unavailable"
message="Error message should be user-friendly"/>
</munit:validation>
</munit:test>
Test Case 4: Shipping API Timeout
This test verifies the flow handles a shipping API timeout and still returns the order ID with a warning.
<munit:test name="createOrder_shippingTimeout_returns202WithWarning"
description="Shipping timeout should still save order, return 202">
<munit:behavior>
<!-- DB succeeds -->
<munit-tools:mock-when processor="db:insert">
<munit-tools:with-attributes>
<munit-tools:with-attribute attributeName="doc:name"
whereValue="Save Order to DB"/>
</munit-tools:with-attributes>
<munit-tools:then-return>
<munit-tools:payload value='[{"GENERATED_KEY": 5043}]'
mediaType="application/java"/>
</munit-tools:then-return>
</munit-tools:mock-when>
<!-- Shipping API times out -->
<munit-tools:mock-when processor="http:request">
<munit-tools:with-attributes>
<munit-tools:with-attribute attributeName="doc:name"
whereValue="Call Shipping API"/>
</munit-tools:with-attributes>
<munit-tools:then-throw>
<munit-tools:error typeId="HTTP:TIMEOUT"
description="Shipping API timed out"/>
</munit-tools:then-throw>
</munit-tools:mock-when>
</munit:behavior>
<munit:execution>
<munit:set-event>
<munit:payload value='{"product": "Bag", "qty": 2, "price": 49.99}'
mediaType="application/json"/>
</munit:set-event>
<flow-ref name="createOrderFlow"/>
</munit:execution>
<munit:validation>
<munit-tools:assert-equals
actual="#[output application/java --- vars.httpStatus]"
expected="#[202]"
message="Should accept order even if shipping API fails"/>
<munit-tools:assert-equals
actual="#[output application/java --- payload.orderId]"
expected="#[5043]"
message="Order ID should still be returned"/>
<munit-tools:assert-not-null
value="#[payload.warning]"
message="Warning about shipping delay should be present"/>
</munit:validation>
</munit:test>
Before and After Each Test
Use munit:before-test and munit:after-test to set up and clean up test state that every test in the suite needs.
<!-- Runs before EVERY test in this suite -->
<munit:before-test name="setup">
<set-variable variableName="correlationId" value="#[uuid()]"/>
<set-variable variableName="testStartTime" value="#[now()]"/>
</munit:before-test>
<!-- Runs after EVERY test in this suite -->
<munit:after-test name="cleanup">
<logger level="INFO"
message="Test completed in #[(now() - vars.testStartTime).milliseconds]ms"/>
</munit:after-test>
Running Tests from Maven
Run MUnit tests from the command line using Maven. This integrates with CI/CD pipelines so tests run automatically on every code push.
Run all tests:
mvn test
Run with coverage report:
mvn test -Dmunit.coverage.runCoverage=true
Fail build if coverage below 80%:
mvn test -Dmunit.coverage.failBuild=true \
-Dmunit.coverage.flowCoverageThreshold=80
Skip tests (deployment only):
mvn deploy -DskipTests
Test Naming Convention
Use a consistent test name format so results are self-documenting. The recommended pattern is: flowName_scenario_expectedResult.
Good test names: createOrderFlow_validInput_returns201 createOrderFlow_nullProduct_returns400 createOrderFlow_dbOffline_returns503 getOrderFlow_existingId_returns200 getOrderFlow_invalidId_returns404 batchSyncFlow_emptySource_completesWithZeroRecords Bad test names: test1 myTest checkOrder
