This is an example of a TypeScript/React "Product" API consumer that uses Mock-Service-Worker, Pact, PactFlow and GitHub Actions to generate and publish Pact consumer contracts.
It performs pre-deployment cross-compatibility checks to ensure that it is compatible with specified providers using the Bi-Directional contract capability of PactFlow.
See the full PactFlow Bi-Directional Workshop for which this can be substituted in as the "consumer".
Get up and running in 3 steps:
# 1. Install dependencies
npm install
# 2. Run tests and generate Pact contract
npm test
# 3. Check the generated contract
ls -la pacts/You should see a Pact contract file generated in the pacts/ directory! This contract represents the consumer's expectations of the Provider API.
Tip
To see the full CI/CD workflow locally, run make fake_ci (requires Docker and these environment variables: PACT_BROKER_BASE_URL, PACT_BROKER_TOKEN)
This project includes a Makefile for convenience, but you can also use npm and Docker commands directly:
| Task | Makefile | Native Commands |
|---|---|---|
| Run tests | make test |
npm test |
| Full CI pipeline | make fake_ci |
Set environment variables and run commands in sequence |
Tip
The Makefile provides a consistent interface across PactFlow's 30+ example repositories. If you're unfamiliar with Make, use the native commands shown in the Usage section below.
Bi-Directional Contract Testing is a contract testing approach that allows you to use your existing mock server (like MSW) and API specifications (like OpenAPI) to verify that services can work together.
How it works:
- Consumer Side (this project): Your tests run against MSW mocks, generating a Pact contract as output that describes what the consumer expects from the provider. This contract is then uploaded to PactFlow.
- Provider Side: The provider has an OpenAPI Specification (OAS) that may be input to their tests. The combination of the test results validating the OAS, plus the OAS itself, forms the provider contract, which is then uploaded to PactFlow.
- PactFlow: Compares the consumer's expectations (Pact) against the provider's specification (OAS) to ensure compatibility
Key Benefits:
- Use Your Existing Tools: Keep using MSW for mocking - no need to learn Pact-specific mocking
- Automated Compatibility Checks: PactFlow automatically validates that your consumer expectations match the provider's API before deployment
- Fast Feedback: Know immediately if a provider change will break your consumer, or if your consumer expects something the provider doesn't offer
- Safe Deployments: The
can-i-deploycheck ensures you only deploy compatible versions
Unlike traditional Pact (which requires both sides to use Pact), bi-directional testing lets each side use their preferred tools while still maintaining contract safety.
This example:
- Implements a React-based "Product" website built with Vite and TypeScript
- Utilizes MSW to mock out the Product API provider
- Utilizes pact-msw-adapter to transform MSW mocks into Pact consumer contracts
This project uses the following web development tools:
- TypeScript - Full type safety with ESNext target
- Vite - Fast build tool and dev server
- Vitest - Modern test runner with native ESM support
- Biome - Fast formatter and linter with TypeScript and React support
- React Router v7 - Latest routing library
- MSW v2 - API mocking for browser and Node.js
In the following diagram, you can see how the consumer testing process works - it's the same as the current Pact process.
When we call "can-i-deploy" the cross-contract validation process kicks off on PactFlow, to ensure any consumer consumes a valid subset of the OAS for the provider.
graph LR
Consumer[Consumer]
Mock[Mock<br/>e.g. Pact]
PactFlow[PactFlow performs check<br/>to confirm that the Pact<br/>contract is compatible with<br/>Provider OAS]
Contract[Consumer<br/>Contract]
Provider[Provider]
ProviderTest[Provider Testing<br/>Tool BYO]
Validation{Cross Contract<br/>Validation}
Consumer -->|1. Run tests| Mock
Mock -->|2. Generates| Contract
Contract -->|3. Upload| PactFlow
Provider -->|Provider<br/>Contract| ProviderTest
ProviderTest -->|OAS| PactFlow
PactFlow -->|4. Validates| Validation
style Consumer fill:#9f6ce6
style Provider fill:#9f6ce6
style PactFlow fill:#1eb5a0
style Validation fill:#5cb85c
Note
The provider contract consists of both the OpenAPI Specification (OAS) and the test results that validate it. The OAS may be used as input to the provider's tests, and the validated OAS is what gets uploaded to PactFlow.
The project uses a Makefile to simulate a very simple build pipeline with two stages - test and deploy. See the table above for native command equivalents, or the detailed Usage section below for native npm and Docker commands.
When you run the CI pipeline (see below for doing this), the pipeline should perform the following activities (simplified):
- Test
- Run tests (including the pact tests that generate the contract)
- Publish pacts, tagging the consumer version with the name of the current branch
- Check if we are safe to deploy to Production with
can-i-deploy(i.e., has the cross-contract validation been successfully performed)
- Deploy (only from master)
- Deploy app to Production
- Record the Production deployment in the Pact Broker
graph TB
subgraph CP["Consumer Pipeline"]
RunTests[Run tests]
Publish[Publish contract]
CanDeploy[can-i-deploy]
Deploy[Deploy!]
RunTests -->|Generate contract| Publish
Publish --> CanDeploy
CanDeploy -->|Approved| Deploy
end
subgraph PF["PactFlow Server"]
Broker[(Contract Broker)]
Validation[Cross-Contract<br/>Validation]
ProviderOAS[Provider OAS<br/>in Production]
Broker -->|Compare| Validation
ProviderOAS -->|Against| Validation
end
Publish -.->|Upload contract| Broker
CanDeploy <-.->|Request validation| Validation
style RunTests fill:#9f6ce6
style Publish fill:#9f6ce6
style CanDeploy fill:#9f6ce6
style Deploy fill:#9f6ce6
style Validation fill:#5cb85c
style CP fill:#2d2d2d,stroke:#9f6ce6,stroke-width:3px
style PF fill:#2d2d2d,stroke:#1eb5a0,stroke-width:3px
Note
The cross-contract validation happens on PactFlow's server when can-i-deploy is called. It compares the consumer contract against the provider's OAS that's already in production to ensure compatibility before deployment.
This project is currently compatible with the following provider(s):
- pactflow-example-bi-directional-provider-dredd
- pactflow-example-bi-directional-provider-restassured
- pactflow-example-bi-directional-provider-postman
- pactflow-example-bi-directional-provider-dotnet
See Environment variables on how to set these up.
Software:
- Tools listed at: https://docs.pactflow.io/docs/workshops/ci-cd/set-up-ci/prerequisites/
- Node.js with ESNext support
- A pactflow.io account with a valid API token
To be able to run some of the commands locally, you will need to export the following environment variables into your shell:
PACT_BROKER_TOKEN: a valid API token for PactFlowPACT_BROKER_BASE_URL: a fully qualified domain name with protocol to your pact broker e.g. https://testdemo.pactflow.io
Set PACT_PROVIDER to one of the following:
PACT_PROVIDER=pactflow-example-bi-directional-provider-dredd: Dredd - (https://github.com/pactflow/example-bi-directional-provider-dredd)PACT_PROVIDER=pactflow-example-bi-directional-provider-postman: Postman - (https://github.com/pactflow/example-bi-directional-provider-postman)PACT_PROVIDER=pactflow-example-bi-directional-provider-restassured: Rest Assured - (https://github.com/pactflow/example-bi-directional-provider-restassured)
Start the development server to see the Product website:
npm install
npm run devThe app will open at http://localhost:3000 and display a list of products.
Run the test suite with Vitest:
npm testThis runs all tests, including the MSW-based API tests that generate the Pact contract which will be located in the pacts/ directory.
Useful commands for development:
# Type checking
npm run type-check
# Check linting and formatting
npm run check
# Variant to auto-fix issues
npm run check:fix
# Production build
npm run build
# Preview production build
npm run preview
# Emulate the full CI pipeline locally (requires Docker, PACT_BROKER_BASE_URL, PACT_BROKER_TOKEN)
make fake_ciCheck out the pact-msw-adapter repo here for more details in the readme.
Key implementation files:
src/mocks/handlers.ts- Defines MSW request handlerssrc/mocks/server.ts- Sets up the MSW server for Node.js testingsrc/mocks/browser.ts- Sets up MSW for browser developmentsrc/setupTests.ts- Configures test mocking and records MSW interactions as Pact contractspublic/mockServiceWorker.js- MSW service worker (generated vianpx msw init public/)
The Makefile is configured to run on Unix-based systems such as you would find in most common CI/CD pipelines.
Commands can be run locally on Unix/Mac, or on Windows via WSL2.
You can still try this example locally on Windows using PowerShell and running commands manually.
Click to see Windows-specific instructions here
These will be the same commands that are used in the Makefile with a few manual tweaks.
-
Make sure you have set all of the environment variables. In PowerShell, they can be set like so:
$env:GIT_BRANCH="main" -
Publish the pact that was generated. The step uses the pact-cli docker image to publish the pact to your pactflow account. The path for
<path_to_project_root>needs to be converted from Windows paths to UNIX ones as the Docker container is using UNIX. Either hard code this or set it as another environment variable.C:\Users\Person\Documents\example-bi-directional-consumer-mswbecomes
/c/Users/Person/Documents/example-bi-directional-consumer-msw$env:VARIABLE_NAMErefers to environment variables in Windows.docker run --rm -v <path_to_project_root>:<path_to_project_root> -e PACT_BROKER_BASE_URL -e PACT_BROKER_TOKEN pactfoundation/pact-cli publish <path_to_pacts_folder> --consumer-app-version $env:GIT_COMMIT --branch $env:GIT_BRANCH -
Check can-i-deploy to see if your provider is compatible with your pact.
docker run --rm -v <path_to_project_root>:<path_to_project_root> -e PACT_BROKER_BASE_URL -e PACT_BROKER_TOKEN pactfoundation/pact-cli broker can-i-deploy --pacticipant pactflow-example-bi-directional-consumer-msw --version $env:GIT_COMMIT --to-environment production --retry-while-unknown 0 --retry-interval 10 -
Have a look at what other commands are available in the Makefile. All of them can be run locally from PowerShell by changing the Windows paths to UNIX format and replacing the environment variable references. Any variable referenced as
${VARIABLE}can be changed to$env:VARIABLEto reference environment variables in PowerShell.
- Consumer Side Bi-Directional Contract Testing Guide
- Provider Side Bi-Directional Contract Testing Guide
Reach out via a GitHub Issue, or reach us over in the Pact foundation Slack