Consumer-driven contract testing example for Swagger Petstore API using Pact JS and TypeScript.
This repository demonstrates how to test API contracts between a consumer and provider without relying on full end-to-end environments.
The project models a small Petstore client that defines expectations for a Petstore API provider. Consumer Pact tests generate a contract file, and provider verification checks that a local Express provider can satisfy that contract.
This is a portfolio-ready baseline project focused on the core Pact workflow:
- consumer tests generate a Pact contract;
- provider verification checks a local provider against that generated contract;
- CI keeps the flow deterministic without relying on the live Petstore demo API;
- dependency versions are pinned to keep clean checkouts reproducible.
Pact Broker, PactFlow, Docker, and richer reporting are intentionally left as future improvements after the basic consumer/provider contract flow is stable.
- TypeScript
- Pact JS
- Jest
- Axios
- Express
- ESLint
- Prettier
- GitHub Actions
Consumer: PetstoreClient
Provider: PetstoreAPI
The example is inspired by Swagger Petstore, a sample API commonly used for OpenAPI demonstrations.
Petstore Web/Mobile Client ---> Petstore API
Consumer Provider
Contract testing verifies that two systems agree on the shape and behavior of their interaction.
Unlike traditional API tests, contract tests do not primarily check whether a deployed API works end-to-end. Instead, they verify whether the provider can satisfy expectations defined by the consumer. In consumer-driven contract testing, the consumer records the behavior it needs, and the provider is verified against that already generated contract.
This reduces dependency on slow, expensive, and brittle end-to-end integration tests while still protecting service integration quality.
| Aspect | API tests | Contract tests |
|---|---|---|
| Main question | Does this deployed API behavior work right now? | Can the provider satisfy the consumer's agreed expectations? |
| Owner of expectations | Usually the test author or API team | The consumer that depends on the provider |
| Typical target | A running API environment | Pact mock server for consumer tests and provider app for verification |
| Assertions | Status codes, response values, workflows, side effects | Request/response shape, required headers, provider states, compatible types |
| Failure signal | The API behavior or environment is currently broken | A consumer/provider integration contract has become incompatible |
This repository is not just a generic API test suite. The consumer tests define the behavior
PetstoreClient needs from PetstoreAPI, and Pact writes those expectations into a contract file.
The provider verification test then checks whether the local provider implementation can satisfy
that already generated contract.
Pact matchers are used instead of full hard-coded JSON equality. For example, the contract checks
that id is a number, photoUrls is an array of strings, and status matches
available|pending|sold. This keeps the contract strict about compatibility without making it
fragile when irrelevant example values change.
This project intentionally does not use the public Petstore API as the CI provider target. The live sample API is useful for learning, but CI should verify a deterministic provider fixture rather than depend on public demo service availability or mutable remote data.
| Interaction | Method | Endpoint |
|---|---|---|
| Get pet by ID | GET | /v2/pet/{petId} |
| Find pets by status | GET | /v2/pet/findByStatus |
| Create pet | POST | /v2/pet |
.
├── .github/workflows/contract-tests.yml
├── docs/
│ ├── contract-testing-basics.md
│ ├── consumer-provider-flow.md
│ ├── future-improvements.md
│ ├── pact-matchers.md
│ ├── provider-states.md
│ └── troubleshooting.md
├── pacts/
├── src/
│ ├── consumer/
│ │ ├── petstoreClient.ts
│ │ └── types.ts
│ ├── provider/
│ │ ├── app.ts
│ │ ├── providerStates.ts
│ │ ├── server.ts
│ │ └── testData.ts
│ └── shared/
│ └── petMatchers.ts
└── tests/
├── consumer/
└── provider/
docscontains deeper explanations for the contract testing flow, Pact matchers, provider states, troubleshooting, and future improvements.src/consumercontains the TypeScript API client and Petstore domain types.src/providercontains a local Express provider stub used for deterministic verification.src/provider/providerStates.tsprepares local provider fixtures for Pact provider states.src/sharedcontains Pact matchers shared by the consumer contract tests.tests/consumergenerates Pact contracts from consumer expectations.tests/providerverifies the local provider against generated Pact files.pactsstores generated contract artifacts for review and CI upload.
- Contract Testing Basics
- Consumer and Provider Flow
- Pact Matchers
- Provider States
- Troubleshooting
- Future Improvements
Use Node.js 22 to match the GitHub Actions runtime.
npm installnpm run test:consumernpm run test:providernpm run test:contractnpm run typecheck
npm run lintConsumer tests generate a Pact file in:
pacts/PetstoreClient-PetstoreAPI.json
The generated contract is intentionally not ignored by Git. For a portfolio repository, keeping the contract visible makes the consumer/provider flow easier to inspect.
Each Pact interaction declares the provider state required for that scenario, such as
pet with ID 123 exists or available pets exist.
During provider verification, Pact calls a local state setup endpoint before each interaction. This project enables that endpoint only in the verification test app:
/_pact/provider-states
The setup handler resets the Express provider fixture data before verification runs. This keeps the provider verification deterministic and makes the provider state lifecycle explicit without exposing test-only setup routes from the normal provider server.
GitHub Actions models the same consumer-driven flow used locally, but splits it into focused jobs:
| Job | Purpose |
|---|---|
quality |
Runs TypeScript type checking and ESLint. |
consumer-contracts |
Runs consumer Pact tests and uploads the generated contract from pacts/. |
provider-verification |
Downloads the Pact artifact and verifies the local provider against it. |
The provider verification job depends on both quality and consumer-contracts. This makes the CI
relationship explicit: provider compatibility is checked against the contract produced by consumer
tests, not against assumptions duplicated in the provider test.
The pipeline uploads the generated Pact contract as a GitHub Actions artifact so the contract can be inspected after the run.
The public Petstore API is useful as a learning reference, but it should not be a hard dependency for CI. Public demo APIs can be unstable, reset data, change behavior, or respond unpredictably.
A stable local provider makes the contract testing pipeline deterministic while still demonstrating the important Pact workflow:
- Consumer tests define expectations.
- Consumer tests generate the Pact contract.
- Provider verification checks compatibility with that contract.
This project demonstrates practical contract testing skills relevant for distributed systems and microservice-based architectures.
It shows how to:
- define consumer expectations;
- generate Pact contracts;
- verify provider compatibility;
- keep tests deterministic in CI;
- avoid overusing slow and brittle end-to-end tests.
Alongside Playwright, k6, and ReqnRoll examples, this repository covers service integration quality without requiring a full environment dependency.
- Add Pact Broker or PactFlow integration
- Add can-i-deploy checks
- Add negative contract scenarios
- Add OpenAPI schema comparison
- Add Docker support
- Add GitHub Pages test report