Skip to content

Commit f69ad45

Browse files
committed
docs: rework examples
Replace the per-version examples with a single suite of examples, all using `Pact` (i.e., `PactV4`). Signed-off-by: JP-Ellis <josh@jpellis.me>
1 parent c31ebb2 commit f69ad45

274 files changed

Lines changed: 83974 additions & 79352 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 32 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,6 @@ jobs:
8686
- name: Format
8787
run: npm run format
8888

89-
build:
90-
name: Package Pact JS
91-
runs-on: ubuntu-latest
92-
93-
steps:
94-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
95-
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
96-
with:
97-
node-version: ${{ env.STABLE_NODE_VERSION }}
98-
cache: npm
99-
- run: npm ci
100-
- run: npm run dist
101-
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
102-
with:
103-
name: dist
104-
path: dist
105-
10689
regression:
10790
name: Run regression suite
10891
runs-on: ubuntu-latest
@@ -246,68 +229,41 @@ jobs:
246229
sh -c "npm ci && npm test"
247230
248231
examples:
249-
name: >-
250-
Test Examples
251-
232+
name: Test Examples
252233
runs-on: ubuntu-latest
253-
needs:
254-
- build
255-
256-
strategy:
257-
fail-fast: false
258-
matrix:
259-
d:
260-
- v2/e2e
261-
- v2/graphql
262-
- v2/jest
263-
- v2/messages
264-
- v2/mocha
265-
- v2/serverless
266-
- v2/typescript
267-
- v3/e2e
268-
- v3/graphql
269-
- v3/provider-state-injected
270-
- v3/run-specific-verifications
271-
- v3/multipart
272-
- v3/with-matching-rules
273-
- v3/todo-consumer
274-
- v3/typescript
275-
- v4/graphql
276-
- v4/matchers
277-
- v4/plugins
278-
- v4/messages
279-
- v4/multipart
280-
- v4/typescript
281-
- v4/with-matching-rules
282234

283235
steps:
284-
- name: Checkout
285-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
286-
287-
- name: Setup Node.js ${{ env.STABLE_NODE_VERSION }}
288-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
236+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
237+
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
289238
with:
290239
node-version: ${{ env.STABLE_NODE_VERSION }}
291240
cache: npm
292-
293-
# Temporary fix for lodash issue. This step should be removed eventually
294-
# once a better solution is found.
295-
- name: Install dependencies
296-
run: npm ci
297-
298-
- name: Install plugins
299-
run: npm run install-plugins
300-
301-
- name: Download dist folder
302-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
303-
with:
304-
name: dist
305-
path: dist
306-
307-
- name: Install dependencies
308-
working-directory: examples/${{ matrix.d }}
309-
run: npm ci
310-
311-
- name: Run tests
312-
working-directory: examples/${{ matrix.d }}
313-
run: npm run test
241+
- run: npm ci
242+
- run: npm run install-plugins
243+
- name: Install and test examples
244+
run: |
245+
error_file=$(mktemp)
246+
find examples \
247+
-maxdepth 2 \
248+
-name package.json \
249+
-not -path '*/node_modules/*' \
250+
-print0 | while IFS= read -r -d '' dir; do
251+
pushd "$(dirname "$dir")" > /dev/null
252+
echo "::group::$PWD"
253+
if ! npm ci; then
254+
echo "npm ci failed in $PWD" >> "$error_file"
255+
fi
256+
if ! npm run test; then
257+
echo "npm run test failed in $PWD" >> "$error_file"
258+
fi
259+
echo "::endgroup::"
260+
popd > /dev/null
261+
done
262+
if [ -s "$error_file" ]; then
263+
echo "::error::Examples test failures occurred"
264+
while IFS= read -r line; do
265+
echo "::error::$line"
266+
done < "$error_file"
267+
rm "$error_file"
268+
exit 1
269+
fi

examples/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# pact-js Examples
2+
3+
This directory contains educational examples for [pact-js](https://github.com/pact-foundation/pact-js), demonstrating consumer-driven contract testing with [Pact](https://docs.pact.io).
4+
5+
**New to Pact? Start with [`http/`](./http/).**
6+
7+
## The Pact Workflow
8+
9+
```mermaid
10+
graph LR
11+
A["Consumer test<br/>Define what you<br/>expect to receive"] -->|Pact file<br/>Written to disk<br/>by consumer test| B["Provider verification<br/>Provider proves it behaves<br/>as the consumer expects"]
12+
```
13+
14+
1. The **consumer** writes a test that defines the interactions it needs. Pact generates a **pact file** (JSON) describing those interactions. In these examples, pact files are written to disk for simplicity.
15+
2. The **provider** runs the pact file against its real implementation, verifying each interaction in isolation.
16+
3. If the provider passes, both sides can deploy independently — neither will break the other.
17+
18+
For team workflows, pact files are published to a Pact Broker (such as [PactFlow.io](https://pactflow.io) or the open source [Pact Broker](https://docs.pact.io/pact_broker)) to enable proper deployment checks like `can-i-deploy`.
19+
20+
## Examples
21+
22+
| Example | What it demonstrates |
23+
| -------------------------------------- | ---------------------------------------------------------------- |
24+
| [`http/`](./http/) | **Start here.** Canonical HTTP consumer/provider |
25+
| [`matchers/`](./matchers/) | Pact matcher library (`like`, `regex`, `eachLike`, etc.) |
26+
| [`graphql/`](./graphql/) | GraphQL over HTTP using `addGraphQLInteraction()` |
27+
| [`messages/`](./messages/) | Async/event-driven messaging with `addAsynchronousInteraction()` |
28+
| [`multipart/`](./multipart/) | File upload and multipart form data |
29+
| [`plugins/`](./plugins/) | Plugin system for custom protocols (gRPC, MATT, etc.) |
30+
| [`provider-state/`](./provider-state/) | Provider states, parameterised states, and `fromProviderState()` |
31+
| [`xml/`](./xml/) | XML response matching with `XmlBuilder` |
32+
33+
## Running an Example
34+
35+
Each example is a standalone npm project:
36+
37+
```bash
38+
cd examples/http
39+
npm install
40+
npm test # runs consumer then provider
41+
npm run test:consumer # generate pact file only
42+
npm run test:provider # verify pact file only
43+
```
44+
45+
## Historical Reference
46+
47+
The previous `v2/`, `v3/`, and `v4/` examples are available in [git history](https://github.com/pact-foundation/pact-js/tree/3e1755a60934b366c2da95b998777c15f0236661/examples) for reference. The regression test suite covering the v2/v3 API surfaces lives in `regression/` at the repository root.

examples/graphql/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# GraphQL Example
2+
3+
This example shows how to use Pact for GraphQL APIs. Pact treats GraphQL as a specialised form of HTTP. The contract captures the exact operation, variables, and response shape expected by the consumer.
4+
5+
## What You'll Learn
6+
7+
- Using `addGraphQLInteraction()` to define a GraphQL contract
8+
- How Pact normalises query whitespace so minor formatting changes don't break contracts
9+
- Provider verification works the same as HTTP — no special GraphQL tools needed
10+
11+
## Running the Example
12+
13+
```bash
14+
npm install
15+
npm test
16+
```
17+
18+
## How It Works
19+
20+
**Consumer** sends a `HelloQuery` operation as a POST to `/graphql`. Pact's `addGraphQLInteraction()` parses the query, records the operation name and variables, and normalises whitespace before matching. This means you can reformat your query string without breaking the contract.
21+
22+
**Provider** implements the GraphQL schema using the `graphql` package and handles requests at `POST /graphql`. The Verifier replays the consumer's POST request and checks that the response matches.
23+
24+
Pact verifies the *contract* between consumer and provider: whether the provider returns the right shape. Testing GraphQL query logic (resolvers, filters, pagination) belongs in the provider's own test suite.
25+
26+
## Further Reading
27+
28+
- [Pact GraphQL support](https://docs.pact.io/implementation_guides/javascript/docs/graphql)

examples/graphql/consumer.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import path from 'node:path';
2+
import {
3+
type LogLevel,
4+
Matchers,
5+
Pact,
6+
SpecificationVersion,
7+
} from '@pact-foundation/pact';
8+
import { describe, expect, it } from 'vitest';
9+
import { fetchHello } from './consumer';
10+
11+
const { like } = Matchers;
12+
13+
/**
14+
* Consumer Pact tests for the GraphQL Hello service.
15+
*
16+
* Pact treats GraphQL as a specialised form of HTTP: the interaction is still
17+
* a POST request, but `addGraphQLInteraction()` parses the query body and
18+
* normalises whitespace and field ordering when matching. You define the exact
19+
* GraphQL operation, variables, and expected response shape.
20+
*
21+
* Important: Pact verifies the GraphQL *contract* (request shape + response
22+
* shape), not the query logic. Use your GraphQL server's own tests for logic.
23+
*/
24+
describe('fetchHello — GraphQL consumer', () => {
25+
const pact = new Pact({
26+
consumer: 'GraphQLConsumer',
27+
provider: 'GraphQLProvider',
28+
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
29+
dir: path.resolve(process.cwd(), 'pacts'),
30+
logLevel: (process.env.LOG_LEVEL as LogLevel) ?? 'warn',
31+
});
32+
33+
it('sends a HelloQuery and receives a greeting', async () => {
34+
await pact
35+
.addGraphQLInteraction()
36+
.given('the hello service is available')
37+
.uponReceiving('a HelloQuery request')
38+
// addGraphQLInteraction() wraps the operation details and generates
39+
// the correct POST body matcher automatically.
40+
.withOperation('HelloQuery')
41+
.withVariables({ name: 'World' })
42+
.withRequest('POST', '/graphql')
43+
.withQuery(`
44+
query HelloQuery {
45+
hello
46+
}
47+
`)
48+
.willRespondWith(200, (builder) => {
49+
builder.headers({ 'Content-Type': 'application/json; charset=utf-8' });
50+
// like() on the greeting string: the provider may return any greeting,
51+
// not specifically "Hello, World!". The contract only requires it to be
52+
// a string at data.hello.
53+
builder.jsonBody({ data: { hello: like('Hello, World!') } });
54+
})
55+
.executeTest(async (mockserver) => {
56+
const greeting = await fetchHello(mockserver.url);
57+
expect(greeting).toBe('Hello, World!');
58+
});
59+
});
60+
});

examples/graphql/consumer.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import axios, { type AxiosResponse } from 'axios';
2+
3+
/** The result of the HelloQuery operation. */
4+
export interface HelloQueryResult {
5+
data: {
6+
hello: string;
7+
};
8+
}
9+
10+
/**
11+
* GraphQL client for the Hello service.
12+
*
13+
* GraphQL queries are sent as HTTP POST requests with a JSON body containing
14+
* `query`, `operationName`, and `variables`. This is a plain HTTP call — no
15+
* GraphQL client library is required for Pact testing.
16+
*
17+
* Pact's `addGraphQLInteraction()` understands the query structure and can
18+
* normalise whitespace and field ordering when matching, which means minor
19+
* formatting differences between consumer and provider don't break the contract.
20+
*/
21+
export async function fetchHello(baseUrl: string): Promise<string> {
22+
const { data }: AxiosResponse<HelloQueryResult> = await axios.post(
23+
`${baseUrl}/graphql`,
24+
{
25+
operationName: 'HelloQuery',
26+
query: `
27+
query HelloQuery {
28+
hello
29+
}
30+
`,
31+
variables: { name: 'World' },
32+
},
33+
{
34+
headers: {
35+
'Content-Type': 'application/json',
36+
Accept: 'application/json',
37+
},
38+
},
39+
);
40+
return data.data.hello;
41+
}

0 commit comments

Comments
 (0)