Keep the Order While Looking Things Up

Use HTTP targets, explicit optional fallbacks and target-aware MUnit mocks to preserve an order during enrichment.

Send A-1001 to a flow that immediately requests the customer record. After Request, payload describes the customer. Selecting payload.orderId at that point asks a customer document for an order field. The missing value is a consequence of replacement, not a mysterious failure of the selector.

A Request target gives the returned value a separate location, allowing the current order to remain the message payload.

Order payload: A-1001: Original context. HTTP target: customer variable: Dana + ACTIVE. Response transform: Order identity: Customer facts. The target separates the dependency result from the retained order.

Open this checkpoint in ACB

Stop the previous application. Open book/checkpoints/10-enrichment-tests from the companion as the project folder, then open src/main/mule/app.xml. Use Flow List to select the named example. Run Run Mule Application from Run and Debug; wait for deployment before sending requests. After a canvas edit, use Save and Hot-deploy to Local Runtime.

Start the controlled dependency with python3 book/stubs/server.py from the companion root in a separate terminal before calling these flows. It listens on port 18882.

The HTTP verification command, run from the companion root while this checkpoint is running, is python3 book/run.py verify 10. It checks the supplied baseline; restore exercise changes before using it.

Put the response beside the order

Example 024 — Keep the order during enrichment

The complete source is in checkpoints/10-enrichment-tests/src/main/mule/app.xml.

Choose Flow List → enrich-order. Select Request and check Connection Config Dependency_HTTP, Method GET and Path /customers/C-42. Open Advanced → Output and set Target Variable to customer; keep Target Value as the expression payload.

Select the following Transform Message and inspect its Payload inline script:

%dw 2.0
output application/json
---
{orderId: payload.orderId, customer: vars.customer.name, status: vars.customer.status}

The Target Variable value customer stores the request’s returned payload in vars.customer. The following transform reads the order identity from payload and the customer fields from the variable.

Post book/fixtures/order-calculation.json to /enriched. The response is:

{"orderId":"A-1001","customer":"Dana","status":"ACTIVE"}

The name status here is the customer’s business status, ACTIVE. It is not the HTTP status code. Names that describe the source make later investigation much easier.

After the successful RequestWhere this flow reads it
Original orderpayload
Customer documentvars.customer
Incoming order-request attributesStill on the caller’s message

By default the target value is the returned payload. When you need the dependency’s response attributes as well, set Advanced → Target Value to the expression message. That stores the returned message, whose payload and attributes must then be selected explicitly. message does not include the caller’s entire variable map. The representation workshop keeps a complete example of that variant.

A target does not create a successful result after a failed request — and if a flow reuses one variable name across several calls, a stale earlier value can be read as this call’s answer during recovery. Give separate dependency results separate names and construct the fallback deliberately.

Recover an optional shipping lookup

Example 025 — Keep an unavailable estimate explicit

The complete source is in checkpoints/10-enrichment-tests/src/main/mule/app.xml.

Choose Flow List → optional-shipping and expand Try. The Request inside it calls /shipping/unavailable through Dependency_HTTP, with Method GET and Target Variable shipping.

In the Try’s Error handler, select On Error Continue and inspect Type HTTP:SERVICE_UNAVAILABLE. Its Set Variable has Name shipping and Value expression {available: false}. Select the Transform Message after Try to inspect the response script:

%dw 2.0
output application/json
---
{orderId: payload.orderId, shipping: vars.shipping}

The controlled /shipping/unavailable endpoint returns HTTP 503. The Request operation treats it as an error, so the Try’s handler sets vars.shipping to an explicit unavailable value. Posting A-1001 to /shipping returns:

{"orderId":"A-1001","shipping":{"available":false}}

The order is retained, and the response does not pretend the estimate is zero. The choice to continue belongs to this endpoint’s contract: the estimate is optional. A mandatory save would use a different policy.

Other non-2xx responses require their own interpretation. For a lookup, an expected 404 can be handled as absence. A success-status-code validator can admit 200 and 404 so the flow branches on the saved response status, or a typed error handler can map the documented not-found error. Accepting 404 changes the control path — it does not turn the error document into a customer. Pick one approach, test both present and absent records, and do not make every failure look like “customer not found.” A 200 whose body reads {"error":"temporarily unavailable"} is the same trap from the other side: a status at the HTTP layer says nothing about whether the body follows the schema this flow expects.

Test the target contract

The real local call established the connection and returned fixture. A MUnit mock lets us test the flow’s reaction to that result without requiring the controlled server in every unit test. Mocks are installed after the application initializes, though — a connector can still need valid startup configuration even when its operations are mocked, so an offline test means the external calls during the scenario are controlled, not that the project has stopped needing its dependencies.

Example 026 — Mock the result the flow reads

The complete source is in checkpoints/10-enrichment-tests/src/test/munit/app-test.xml.

Open src/test/munit/app-test.xml and select retains-order-and-customer in the test canvas. In Behavior, Set Event supplies the same JSON order as chapter 6. The next component is Mock when.

Mock settingValue
Processorhttp:request
Attribute to matchtarget
Attribute’s Where value expression'customer'
Then return → Variables → Keycustomer
Returned variable’s Value expression{name: 'Dana', status: 'ACTIVE'}

The mock returns a variable, so keep the original payload in Set Event. In Execution, Flow Reference calls enrich-order. In Validation, inspect these three checks:

ComponentExpression or processorExpected value
Assert thatpayload.orderIdMunitTools::equalTo('A-1001')
Assert thatpayload.customerMunitTools::equalTo('Dana')
Verify callhttp:requestTimes 1

Run the suite from Testing, as in chapter 6. Compare the actual assertion values and invocation count before editing any expectation.

The mock matches the HTTP Request whose target is customer. Its return value supplies a variable named customer, which is where the application reads the operation’s result. The caller’s order remains available for the final transform.

A mock that simply replaces the payload with a customer document would undo the very behavior under test. The test could then fail for an artificial reason, or a rewritten flow could accidentally start depending on that inaccurate arrangement. Read the real operation’s target contract before choosing the mock’s return shape.

Matching on the target costs a few more lines than intercepting every http:request, and it is worth them. The broad matcher would let a second lookup receive the customer fixture, or hide an accidental extra network call — this one names the single interaction it stands in for, and verify-call then checks that the interaction happened.

The assertions check order identity, customer name and one request invocation. Those answer different questions. Preserving A-1001 checks caller context, reading Dana checks the dependency result, and the call count checks the interaction. None proves TLS trust or that a real customer service always returns this shape, and an internal flow reference cannot prove that /enriched is configured correctly either — retain the HTTP integration check for the actual boundary.

Extract only what the application needs

Saving the whole message is useful when response metadata matters. Saving only the payload is simpler when it does not. Keeping an entire request body in several variables also makes a later flow harder to read — even when the event model shares rather than immediately copies the content.

The name response becomes ambiguous as soon as a flow has two calls, so name variables by role instead: order, customer, catalogue, shipping. A small state table beside a transform often exposes that ambiguity before a test does.

MUnit Mock when matching the HTTP Request whose target attribute is customer.

Mock when matches the Request by its customer target attribute. The returned variable from the table above is configured further down the same panel, below what this view shows.

Try it

1. Read a message target. If the target is configured with the Target Value expression message, how must the customer-name selector change?

Show answer

The saved target is now a message, so use vars.customer.payload.name. The dependency HTTP status is under vars.customer.attributes.statusCode. Update the mock and assertions to reflect that different saved shape.

2. Make shipping mandatory. What needs to change besides the error body?

Show answer

The failure must stop the operation that requires shipping. Replace the local Continue policy with a propagated failure and a deliberate request-boundary status. Tests should assert that subsequent mandatory work does not run.

3. Remove the target in the real flow. What should the current test expose?

Show answer

The flow loses its original order payload and no longer has the same target assignment. Its final transform cannot satisfy both the retained identity and dependency-result assertions. Adjusting the mock to hide that change would weaken the test.

Enrichment gives the order information owned elsewhere. Next we give the result a place to be stored and independently read back.

Next: Save It, Then Read It Back

Comments