https://github.com/pactflow/example-bi-directional-provider-asyncapi
Example AsyncAPI project
https://github.com/pactflow/example-bi-directional-provider-asyncapi
Last synced: 15 days ago
JSON representation
Example AsyncAPI project
- Host: GitHub
- URL: https://github.com/pactflow/example-bi-directional-provider-asyncapi
- Owner: pactflow
- Created: 2026-05-28T01:26:38.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-28T03:42:58.000Z (about 1 month ago)
- Last Synced: 2026-05-28T05:12:23.704Z (about 1 month ago)
- Language: TypeScript
- Size: 71.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Example: Bi-Directional Contract Testing with AsyncAPI
> **Early access demo** — PactFlow's AsyncAPI support for bi-directional contract
> testing is not yet publicly available. This demo shows how it works.
## What is this?
This repository demonstrates how to write **Pact consumer tests for a
message-based service** and verify the provider contract using **PactFlow's
AsyncAPI bi-directional contract testing (BDCT)** feature.
Instead of running the provider application, BDCT compares the generated Pact
file against the provider's **AsyncAPI document**.
### Two messaging patterns are demonstrated
| Pattern | AsyncAPI action | Pact interaction type |
|---|---|---|
| **Fire-and-forget** | `receive` | `Asynchronous/Messages` |
| **Request/Reply** | `send` + `reply` | `Synchronous/Messages` |
---
## Project layout
```
.
├── src/
│ ├── consumer.ts # User Service event handlers (consumer code)
│ └── consumer.test.ts # Pact V4 consumer tests
├── provider/
│ └── asyncapi.yaml # AsyncAPI 3.1.0 document (provider contract)
├── scripts/
│ └── verify-provider.mjs # Runs the comparator against pacts + AsyncAPI doc
├── pacts/ # Generated Pact files (git-ignored)
├── package.json
└── vitest.config.ts
```
---
## Quick start
> **Pre-release dependency** — AsyncAPI support in `@pactflow/openapi-pact-comparator`
> is not yet published to npm. `package.json` references the GitHub main branch.
```bash
# Install dependencies (includes the pre-release comparator from GitHub)
npm install
# Step 1 — run consumer tests → generates ./pacts/UserServiceConsumer-UserService.json
npm run test:consumer
# Step 2 — verify the Pact file against the AsyncAPI document
npm run verify:provider
# OR, run both sequentially with...
# npm t
```
---
## How it works
### Consumer side
The consumer tests (`src/consumer.test.ts`) use **Pact V4** (`@pact-foundation/pact`)
to describe the messages the consumer expects. Each interaction is linked to
the relevant **AsyncAPI operation** via the `.reference()` call:
```ts
pact
.addAsynchronousInteraction()
.reference('AsyncAPI', 'operationId', 'receiveUserEvents')
.expectsToReceive('a user-created event', (builder) => {
builder.withJSONContent({ userId: string('u-abc-123'), email: string('...') });
})
.executeTest(v4SynchronousBodyHandler(handleUserCreated));
```
Running the tests writes a Pact file to `./pacts/` with a `comments.references`
block that the comparator uses to look up the correct AsyncAPI operation:
```json
{
"comments": {
"references": {
"AsyncAPI": {
"operationId": "receiveUserEvents",
}
}
}
}
```
### Provider side
The provider only needs to publish its **AsyncAPI document**
(`provider/asyncapi.yaml`). No code runs. The comparator checks that every
interaction in the Pact file is compatible with the schema defined in the spec.
```bash
npm run verify:provider
```
This calls `@pactflow/openapi-pact-comparator` programmatically against:
- `provider/asyncapi.yaml` — the provider's AsyncAPI spec
- `pacts/UserServiceConsumer-UserService.json` — the generated consumer contract
---
## AsyncAPI document overview
```mermaid
flowchart LR
consumer["UserServiceConsumer"]
provider["UserService"]
subgraph broker["Message broker / logical channels"]
direction TB
events(["user-events"])
requests(["user-get-requests"])
responses(["user-get-responses"])
end
provider -- "publish lifecycle events" --> events
events -- "deliver events" --> consumer
consumer -- "send getUserRequest
payload: userId" --> requests
requests -- "route request" --> provider
provider -- "send getUserResponse
payload: userId, name, email" --> responses
responses -- "deliver reply" --> consumer
classDef app fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0f172a;
classDef service fill:#eef2ff,stroke:#6366f1,stroke-width:2px,color:#0f172a;
classDef eventStream fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#0f172a;
classDef request fill:#e0f2fe,stroke:#06b6d4,stroke-width:2px,color:#0f172a;
classDef response fill:#d1fae5,stroke:#10b981,stroke-width:2px,color:#0f172a;
class consumer app;
class provider service;
class events eventStream;
class requests request;
class responses response;
```
The `provider/asyncapi.yaml` defines:
- **`userEvents` channel** (`user-events`) — carries `userCreated` and
`userDeleted` events consumed by downstream services.
- **`getUserRequests` / `getUserResponses` channels** — used for synchronous
user lookup via request/reply (AsyncAPI 3.x `reply` block).
### Operations
```
receiveUserEvents action: receive → fire-and-forget events
getUser action: send → request/reply lookup
```