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 schema

Drift 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.

  1. Describe the Operation

    A request/reply operation needs two channels and a reply block. The reply.address.location tells 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: string

    The reply block is what tells Drift this is a request/reply operation. Without it, the operation would be treated as async-observe.

  2. 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 target names the request message. Drift derives the reply message from the operation's reply block.The expected block 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 expected are compared in addition to schema validation.

  3. 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 expected block describes the request. It must describe the reply.

Mode was async-observe, not request/reply

The operation has no reply block, and no reply-channel parameter was set

Service did not know where to reply

The request message schema has no reply-to header, or the operation's reply.address.location is missing

See Debugging Test Cases.

See Also

Publication date: