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

Leave a Comment

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