Testing Request/Reply Interactions
A request/reply interaction sends a message and waits for a correlated response on another channel. It is synchronous from your test's point of view, even though both messages travel over a message broker.
Drift publishes the request, waits for the reply, and validates the reply against the reply message schema in your AsyncAPI document.
How It Works
Diagram
autonumber
participant D as Drift
participant RQ as Request channel
participant S as Your service
participant RP as Reply channel
D->>RP: Subscribe to the reply channel
Note over D,RP: Drift subscribes before publishing,<br/>so a fast reply cannot be missed
D->>RQ: Publish the request<br/>with correlation ID and reply-to
RQ-->>S: Deliver the request
S->>S: Process the request
S->>RP: Publish the correlated reply
RP-->>D: Deliver the matching reply
D->>D: Validate the reply payload and headers<br/>against the AsyncAPI schemaDrift selects async-request-reply automatically when the AsyncAPI operation declares a reply. You do not need a trigger or a probe: publishing the request is what causes the behavior, and the reply is the evidence.
Describe the Operation
A request/reply operation needs two channels and a
replyblock. Thereply.address.locationtells Drift where to put the reply address on the outgoing request.asyncapi: 3.1.0 info: title: Pricing service version: 1.0.0 defaultContentType: application/json servers: local: host: localhost:9092 protocol: kafka channels: priceQuoteRequest: address: drift.examples.price-quote.request messages: priceQuoteRequest: $ref: "#/components/messages/priceQuoteRequest" priceQuoteReply: address: drift.examples.price-quote.reply messages: priceQuoteReply: $ref: "#/components/messages/priceQuoteReply" operations: requestPriceQuote: action: send title: Request price quote summary: Sends a quote request and receives a correlated reply. channel: $ref: "#/channels/priceQuoteRequest" messages: - $ref: "#/channels/priceQuoteRequest/messages/priceQuoteRequest" reply: address: location: "$message.header#/reply-to" channel: $ref: "#/channels/priceQuoteReply" messages: - $ref: "#/channels/priceQuoteReply/messages/priceQuoteReply" components: messages: priceQuoteRequest: name: priceQuoteRequest headers: type: object properties: correlation-id: type: string reply-to: type: string required: - correlation-id - reply-to payload: type: object required: - requestType - sku - quantity properties: requestType: type: string const: price.quote sku: type: string quantity: type: integer minimum: 1 priceQuoteReply: name: priceQuoteReply headers: type: object properties: correlation-id: type: string required: - correlation-id payload: type: object required: - responseType - sku - quantity - quotedUnitPrice - quotedTotalPrice - currency - availability properties: responseType: type: string const: price.quote sku: type: string quantity: type: integer minimum: 1 quotedUnitPrice: type: number quotedTotalPrice: type: number currency: type: string const: USD availability: type: stringThe
replyblock is what tells Drift this is a request/reply operation. Without it, the operation would be treated asasync-observe.Write the Test Case
# yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json drift-testcase-file: v1 title: "Pricing service request/reply" sources: - name: async-send-receive path: ../asyncapi/send-receive.asyncapi.yaml plugins: - name: asyncapi - name: kafka - name: json operations: RequestPriceQuote_SendReceive: target: async-send-receive:requestPriceQuote:priceQuoteRequest description: "Publish a price.quote request and capture the correlated reply" parameters: timeout-ms: 5000 correlation-id: request-price-quote-001 payload: requestType: price.quote sku: SKU-RED-CHAIR quantity: 3 headers: correlation-id: ${parameters.correlation-id} expected: payload: responseType: price.quote sku: ${parameters.payload.sku} quantity: ${parameters.payload.quantity} quotedUnitPrice: 149.0 quotedTotalPrice: 447.0 currency: USD availability: in-stock headers: correlation-id: ${parameters.correlation-id}The
targetnames the request message. Drift derives the reply message from the operation'sreplyblock.Theexpectedblock describes the reply, not the request.Note
Drift automatically validates the captured payload against the message payload schema in your AsyncAPI document, and the captured headers against the headers schema when one is defined. Values you state under
expectedare compared in addition to schema validation.Run It
drift verify -f drift/send-receive.testcases.yaml
Your service must be running and subscribed to the request channel before you start the run. Drift publishes a request and waits — it does not start your service.
Correlation and the Reply Address
Two pieces of routing information travel on the request. The correlation ID matches the reply to the request. Set it with the correlation-id parameter, and also set it as a header when the request message schema declares one:
parameters:
correlation-id: request-price-quote-001
headers:
correlation-id: ${parameters.correlation-id}Your service must echo the same correlation ID on the reply. Drift filters replies by that value and ignores anything else on the reply channel.
The reply address tells your service where to publish. Drift sets it from the reply.address.location in the operation. With the common $message.header#/reply-to location, Drift sets a reply-to header on the request containing the reply channel address, so your service does not need the address hard-coded.
Setting the Capture Window
timeout-ms bounds how long Drift waits for the reply.
parameters: timeout-ms: 5000
Because request/reply involves a full round trip through your service, allow more time than you would for a simple observe. If the window closes first, the operation fails with a capture timeout.
Naming a Reply Channel Not in the Document
When your operation does not declare a reply but you want to verify one anyway, set reply-channel and execution-mode:
parameters: execution-mode: async-request-reply reply-channel: drift.examples.price-quote.reply
Drift validates the precondition before contacting the broker: async-request-reply needs either a reply in the document or a reply-channel parameter.
Prefer declaring the reply in the AsyncAPI document. That makes the contract complete for consumers as well as for Drift.
Limitations
The reply must arrive on the same transport as the request. Cross-transport replies — a Kafka request answered over HTTP, for example - are not supported.
One request, one reply. Drift captures the first reply matching the correlation ID. It does not verify streams of replies.
Troubleshooting
Symptom | Likely cause |
|---|---|
Capture timeout on the reply | Your service is not running, or is not subscribed to the request channel |
Capture timeout, service logged a reply | The reply carries a different correlation ID, or went to a different channel than the document declares |
Drift validated the request, not the reply | The |
Mode was | The operation has no |
Service did not know where to reply | The request message schema has no |
See Debugging Test Cases.