Verifying Your First AsyncAPI Operation
In this tutorial you verify that a service publishes an event matching its AsyncAPI contract. You start a local Kafka broker, write a Drift test case, run it, then break it on purpose to see what a failure looks like.
You need about 15 minutes.
Prerequisites
Drift installed. See the Installation Guide.
Docker, for the local Kafka broker
Python 3, for the sample publisher
Create a working directory:
mkdir drift-asyncapi-tutorial && cd drift-asyncapi-tutorial mkdir asyncapi drift hooks
Step 1: Start a Kafka Broker
Drift talks to a real broker. Create compose.yaml:
services:
kafka:
image: confluentinc/cp-kafka:latest
container_name: drift-tutorial-kafka
ports:
- "9092:9092"
environment:
KAFKA_ENABLE_KRAFT: "yes"
KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
KAFKA_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093"
KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://localhost:9092"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093"
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
ALLOW_PLAINTEXT_LISTENER: "yes"Start it:
docker compose up -d
Confirm the broker is up:
docker compose ps
Step 2: Create the Topic
Create the topic Drift will subscribe to:
docker exec drift-tutorial-kafka kafka-topics \ --bootstrap-server localhost:9092 \ --create --if-not-exists \ --topic drift.examples.order-created \ --partitions 1 --replication-factor 1
Step 3: Write the AsyncAPI Document
This describes a service that publishes an order.created event. Save it as asyncapi/send.asyncapi.yaml:
asyncapi: 3.1.0
info:
title: Drift AsyncAPI tutorial
version: 1.0.0
defaultContentType: application/json
servers:
local:
host: localhost:9092
protocol: kafka
description: Local Kafka broker
channels:
orderCreated:
address: drift.examples.order-created
messages:
orderCreated:
$ref: "#/components/messages/orderCreated"
operations:
sendOrderCreated:
action: send
title: Send order created
summary: Publishes an order.created event.
channel:
$ref: "#/channels/orderCreated"
messages:
- $ref: "#/channels/orderCreated/messages/orderCreated"
components:
messages:
orderCreated:
name: orderCreated
title: Order created event
correlationId:
location: "$message.header#/correlation-id"
headers:
type: object
properties:
correlation-id:
type: string
required: [correlation-id]
payload:
type: object
required: [eventType, orderId, customerId, status]
properties:
eventType:
type: string
const: order.created
orderId:
type: string
customerId:
type: string
status:
type: string
const: createdTwo details matter:
action: send describes what your application does. Your service is the publisher, so Drift's job is to subscribe and capture. Drift's role is always the opposite of your service's.
correlationId tells Drift where to find the identifier that marks the message as the one this test is waiting for.
Step 4: Write the Trigger
Drift cannot make your service publish. A trigger hook does that. In a real project the trigger would call your service. For this tutorial it publishes directly. Save it as hooks/trigger-send.py:
#!/usr/bin/env python3
"""Publish one order.created event to Kafka."""
import argparse
import json
from kafka import KafkaProducer
parser = argparse.ArgumentParser()
parser.add_argument("--correlation-id", required=True)
parser.add_argument("--order-id", required=True)
parser.add_argument("--customer-id", required=True)
args = parser.parse_args()
producer = KafkaProducer(bootstrap_servers="localhost:9092")
producer.send(
"drift.examples.order-created",
value=json.dumps({
"eventType": "order.created",
"orderId": args.order_id,
"customerId": args.customer_id,
"status": "created",
}).encode(),
headers=[("correlation-id", args.correlation_id.encode())],
)
producer.flush()
print(f"published order.created for {args.correlation_id}")Install the dependency:
python3 -m venv .venv source .venv/bin/activate pip install kafka-python
The trigger must publish the same correlation ID Drift is filtering on. Drift passes it in as an argument, so the two cannot drift apart.
Step 5: Write the Drift Test Case
Save this as drift/send.testcases.yaml:
# yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
drift-testcase-file: v1
title: "AsyncAPI Kafka send flow"
description: |
Observe an outbound order.created event after a trigger hook fires.
sources:
- name: async-send
path: ../asyncapi/send.asyncapi.yaml
plugins:
- name: asyncapi
- name: kafka
- name: json
operations:
SendOrderCreated_Observe:
target: async-send:sendOrderCreated:orderCreated
description: "Observe the order.created event emitted after the trigger hook runs"
parameters:
timeout-ms: 5000
correlation-id: send-order-created-001
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}
- --customer-id
- ${parameters.customerId}
timeout-ms: 2000
expected:
headers:
correlation-id: ${parameters.correlation-id}
payload:
eventType: order.created
orderId: ${parameters.orderId}
customerId: ${parameters.customerId}
status: createdWhat each part does:
Part | Purpose |
|---|---|
| Points Drift at the AsyncAPI document |
|
|
| Selects the operation and message, as |
| Values for this test, including the correlation ID and the capture window |
| The command that makes the event happen |
| What the captured message must contain |
Step 6
Run the Verification
drift verify -f drift/send.testcases.yaml
Drift subscribes to drift.examples.order-created, runs the trigger, waits for a message carrying send-order-created-001, then validates it.
You should see the operation pass.
sequenceDiagram
autonumber
participant D as Drift
participant B as Kafka
participant T as trigger-send.py
D->>B: Subscribe to drift.examples.order-created
D->>T: Run the trigger
T->>B: Publish order.created
B-->>D: Deliver the matching message
D->>D: Validate against the AsyncAPI schema<br/>and your expected blockStep 7: Break It on Purpose
Seeing a failure is as useful as seeing a pass.
In drift/send.testcases.yaml, change the expected status:
expected:
payload:
status: shipped # the service actually publishes "created"Run it again:
drift verify -f drift/send.testcases.yaml
Drift captures the message and reports a mismatch on status, showing what it expected and what it received.
Change it back to created and confirm the test passes again.
Try a Capture Timeout
Hard-code a different correlation ID in the trigger arguments, so the trigger publishes one value while Drift filters for another:
trigger:
parameters:
args:
- ../hooks/trigger-send.py
- --correlation-id
- a-different-id # was ${parameters.correlation-id}Drift now waits the full five seconds and fails with a capture timeout. This is the most common failure when writing your first AsyncAPI test, and it almost always means the trigger published a different correlation ID than the one Drift was filtering on.
Restore ${parameters.correlation-id} before moving on.
Step 8: Clean Up
docker compose down
What You Learned
Drift verifies message-based APIs over a real broker. You provide the transport.
action: sendmeans your service publishes, so Drift subscribes and captures. Drift's role is always the opposite of your service's.A trigger hook causes the behavior Drift observes, and must carry the same correlation ID.
Drift validates the captured message against your AsyncAPI schema and your
expectedblock.
Next Steps
AsyncAPI - the full test case reference
Using Triggers and Probes - testing services that consume messages