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 validationEvery AsyncAPI test file needs three plugins:
Plugin | Purpose |
|---|---|
| Loads AsyncAPI 3.x documents and exposes their operations as targets |
| Connects to the broker and moves messages |
| 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: createdExecution 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 |
|---|---|---|---|
| N/A |
| Subscribes and captures the message your service publishes |
| N/A |
| Publishes the message, then runs your probe |
|
|
| 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 |
|---|---|---|
| map | The message payload Drift publishes |
| map | Message headers Drift sets on publish |
| string | Identifies the message for this test |
| integer | The capture window in milliseconds. Defaults to 30000. |
| string | Selects |
| string | Overrides the automatic mode selection |
| 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 |
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 | 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 brokerOverride 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: createdWhen 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.