Async API

Drift verifies message-based APIs described by AsyncAPI 3.x documents, over Kafka, Amazon SNS, and Amazon SQS. This page covers the mechanics common to every AsyncAPI test case. The sub-pages cover hooks and the two interaction patterns.

Unlike mock-based approaches, Drift communicates over the real transport. You must have a running broker before you can verify anything. For the reasoning behind that, see How Drift Verifies Message-Based APIs.

Prerequisites

  • Drift installed

  • An AsyncAPI 3.x document describing the operations you want to verify

  • A running broker that Drift and your service can both reach

  • The channels, topics, or queues in your document already created

A local broker started with Docker Compose or Testcontainers, or a local emulator such as LocalStack, is the recommended setup.

Declaring the Source and Plugins

Point Drift at your AsyncAPI document with a sources entry, then declare the plugins for your specification, transport, and payload format.

# yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
drift-testcase-file: v1
title: "Order service AsyncAPI verification"

sources:
  - name: order-async
    path: ./asyncapi/order.asyncapi.yaml

plugins:
  - name: asyncapi     # loads the AsyncAPI document and plans the interaction
  - name: kafka        # the transport
  - name: json         # payload validation

Every AsyncAPI test file needs three plugins:

Plugin

Purpose

asyncapi

Loads AsyncAPI 3.x documents and exposes their operations as targets

kafka or aws-messaging

Connects to the broker and moves messages

json

Builds and validates message payloads

Use aws-messaging in place of kafka for Amazon SNS and SQS. See AWS Messaging Plugin.

Targeting an Operation

An AsyncAPI target has up to three segments:

target: <source-name>:<operation-id>:<message-id>
# Full form, naming the message explicitly
target: order-async:sendOrderCreated:orderCreated

The message segment selects which message to use when an operation defines more than one. You can omit it when the operation defines exactly one message:

target: order-async:sendOrderCreated

If an operation defines several messages and you omit the segment, Drift fails during planning and tells you which messages are available.

A Complete Test Case

operations:
  SendOrderCreated_Observe:
    target: order-async:sendOrderCreated:orderCreated
    description: "Observe the order.created event emitted after the trigger runs"
    parameters:
      correlation-id: send-order-created-001
      timeout-ms: 5000
      orderId: ORD-100
      customerId: CUST-9
    trigger:
      executable-type: command
      value: python3
      parameters:
        args:
          - ./hooks/trigger-send.py
          - --correlation-id
          - ${parameters.correlation-id}
          - --order-id
          - ${parameters.orderId}
      timeout-ms: 2000
    expected:
      headers:
        correlation-id: ${parameters.correlation-id}
      payload:
        eventType: order.created
        orderId: ${parameters.orderId}
        customerId: ${parameters.customerId}
        status: created

Execution Modes

Drift chooses how to run the operation based on the AsyncAPI action and your inputs. You do not normally set this yourself.

AsyncAPI action

Your inputs

Mode

What Drift does

send

N/A

async-observe

Subscribes and captures the message your service publishes

receive

N/A

async-inject

Publishes the message, then runs your probe

receive

probe-topic set

async-inject-capture

Publishes, then captures the result from a probe topic

Any

Reply declared in the document

async-request-reply

Publishes a request and captures the correlated reply

AsyncAPI 3 actions are written from your application's perspective. An operation with action: send means your service publishes, so Drift captures. An operation with action: receive means your service consumes, so Drift publishes. Drift's role is always the opposite of your service's.

For the full definition of each mode, see AsyncAPI Execution Modes.

Parameters

These parameters apply to AsyncAPI operations. Set them under parameters.

Parameter

Type

Purpose

payload

map

The message payload Drift publishes

headers

map

Message headers Drift sets on publish

correlation-id

string

Identifies the message for this test

timeout-ms

integer

The capture window in milliseconds. Defaults to 30000.

probe-topic

string

Selects async-inject-capture and names the topic to capture from

execution-mode

string

Overrides the automatic mode selection

reply-channel

string

Names a reply channel when the document does not declare one

Transport plugins accept their own parameters as well. See the Kafka Plugin and AWS Messaging Plugin plugin pages.

Setting the Correlation ID

When Drift subscribes to a channel, other traffic may be present. Correlation tells Drift which message is the one it is waiting for.

Declare the location in your AsyncAPI message. This is the part you must supply - it tells Drift where to read and write the identifier, and correlation is inactive without it.

components:
  messages:
    orderCreated:
      correlationId:
        location: "$message.header#/correlation-id"

The value is optional. Drift resolves it in this order:

Priority

Source

Priority 1 (highest)

The AsyncAPI document, when it sets a value

2

The correlation-id parameter in your test case

3 (lowest)

A UUID Drift generates for the operation

Set the value yourself when something outside the operation needs the same identifier - a trigger hook, a probe command, or an expectation. Omit it and let Drift generate one when nothing else needs to know it:

parameters:
  correlation-id: send-order-created-001
expected:
  headers:
    correlation-id: ${parameters.correlation-id}

Using ${parameters.correlation-id} rather than repeating the literal keeps the trigger hook, the published message, and the expectation in step.

Where you do set a value, give each operation a distinct one. Reusing a value across operations lets a message from an earlier test satisfy a later one.

Setting the Capture Window

timeout-ms bounds how long Drift waits for a message before failing.

parameters:
  timeout-ms: 5000

Too short produces flaky failures against a slow service. Too long makes a genuinely broken service slow to report. A few seconds is a reasonable starting point for a local broker.

Hooks have their own timeout-ms, which bounds the hook process rather than the capture. When a hook omits it, the operation-level timeout-ms applies.

Where Connection Details Come From

Drift resolves connection details from three places. Higher priority wins.

Priority

Source

Use it for

1 (highest)

Test case or global test data

Per-test overrides, and making the target visible to readers

2

Plugin configuration

Stable environment defaults and secrets

3 (lowest)

The servers block in your AsyncAPI document

Portable defaults committed alongside the contract

Committing a local server to your AsyncAPI document gives every developer a working default:

servers:
  local:
    host: localhost:9092
    protocol: kafka
    description: Local Kafka broker

Override it per environment through plugin configuration, and per test through the test case when you need to.

Writing Expectations

The expected block states what a pass looks like. Its shape depends on whether Drift captures a message or reads probe output.

When Drift captures a message - async-observe, async-inject-capture, and async-request-reply - use payload and headers:

expected:
  headers:
    correlation-id: ${parameters.correlation-id}
  payload:
    eventType: order.created
    status: created

When Drift reads probe output - async-inject - the expected block matches the JSON your probe returns:

expected:
  status: processed
  correlationId: ${parameters.correlation-id}

Drift also validates captured messages against the schemas in your AsyncAPI document. The expected block adds value assertions on top of schema conformance, so you do not need to restate every field the schema already covers.

See Also

Publication date: