Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]
node-version: [20, 22, 24]
os:
- macos-13
- macos-latest
Expand Down Expand Up @@ -146,7 +146,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
node-version: [18, 20, 22]
node-version: [20, 22, 24]

steps:
- name: Checkout
Expand All @@ -170,7 +170,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]
node-version: [20, 22, 24]
os:
- ubuntu-24.04-arm
- macos-latest
Expand Down Expand Up @@ -250,20 +250,25 @@ jobs:
fail-fast: false
matrix:
d:
- e2e
- graphql
- jest
- messages
- mocha
- serverless
- typescript
- v2/e2e
- v2/graphql
- v2/jest
- v2/messages
- v2/mocha
- v2/serverless
- v2/typescript
- v3/e2e
- v3/graphql
- v3/provider-state-injected
- v3/run-specific-verifications
- v3/todo-consumer
- v3/typescript
- v4/graphql
- v4/matchers
- v4/plugins
- v4/messages
- v4/multipart
- v4/typescript

steps:
- name: Checkout
Expand Down
98 changes: 50 additions & 48 deletions docs/consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ In this document, we will cover steps 1-3.
To use the library on your tests, add the pact dependency:

```javascript
const { PactV4 } = require("@pact-foundation/pact")
const { Pact } = require("@pact-foundation/pact")
```

`PactV4` is the latest version of this library, supporting up to and including version 4 of the [Pact Specification](https://github.com/pact-foundation/pact-specification/). It also allows interactions of multiple types (HTTP, async, synchronous). For previous versions, see below.
`PactV4` aliased as `Pact` is the latest version of this library, supporting up to and including version 4 of the [Pact Specification](https://github.com/pact-foundation/pact-specification/). It also allows interactions of multiple types (HTTP, async, synchronous). For previous versions, see below.

<details><summary>Previous versions</summary>


```javascript
const { Pact } = require("@pact-foundation/pact") // Supports up to and including Pact Specification version 2
const { PactV2 } = require("@pact-foundation/pact") // Supports up to and including Pact Specification version 2
const { PactV3 } = require("@pact-foundation/pact") // Supportsu up to and including Pact Specification version 3
```

You should use the `PactV4` interface unless you can't, and set the specification version via `spec` to the desired serialisation format.
You should use the `Pact` interface unless you can't, and set the specification version via `spec` to the desired serialisation format.

</details>

The `PactV4` class provides the following high-level APIs, they are listed in the order in which they typically get called in the lifecycle of testing a consumer:
The `Pact` class provides the following high-level APIs, they are listed in the order in which they typically get called in the lifecycle of testing a consumer:

### API

Expand All @@ -48,7 +48,7 @@ The Pact SDK uses a fluent builder to create interactions.

| API | Options | Description |
| -------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `new PactV4(options)` | See constructor options below | Creates a Mock Server test double of your Provider API. The class is **not** thread safe, but you can run tests in parallel by creating as many instances as you need. |
| `new Pact(options)` | See constructor options below | Creates a Mock Server test double of your Provider API. The class is **not** thread safe, but you can run tests in parallel by creating as many instances as you need. |
| `addInteraction(...)` | `V4UnconfiguredInteraction` | Start a builder for an HTTP interaction |
| `addSynchronousInteraction(...)` | `V4UnconfiguredSynchronousMessage` | Start a builder for an asynchronous message |

Expand Down Expand Up @@ -93,61 +93,63 @@ _NOTE: you must also ensure you clear out your pact directory prior to running t
Check out the [examples](https://github.com/pact-foundation/pact-js/tree/master/examples/) for more of these.

```js
import { PactV4, MatchersV3 } from '@pact-foundation/pact';

// Create a 'pact' between the two applications in the integration we are testing
const provider = new PactV4({
dir: path.resolve(process.cwd(), 'pacts'),
consumer: 'MyConsumer',
provider: 'MyProvider',
spec: SpecificationVersion.SPECIFICATION_VERSION_V4, // Modify this as needed for your use case
});
/* tslint:disable:no-unused-expression object-literal-sort-keys max-classes-per-file no-empty */
import * as chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import * as path from 'path';
import sinonChai from 'sinon-chai';
import { Pact, Matchers, SpecificationVersion } from '@pact-foundation/pact';

// API Client that will fetch dogs from the Dog API
// This is the target of our Pact test
public getMeDogs = (from: string): AxiosPromise => {
return axios.request({
baseURL: this.url,
params: { from },
headers: { Accept: 'application/json' },
method: 'GET',
url: '/dogs',
});
};
const expect = chai.expect;
import { DogService } from '../src/index';
const { eachLike } = Matchers;

const dogExample = { dog: 1 };
const EXPECTED_BODY = MatchersV3.eachLike(dogExample);
chai.use(sinonChai);
chai.use(chaiAsPromised);
const LOG_LEVEL = process.env.LOG_LEVEL || 'TRACE';

describe('GET /dogs', () => {
it('returns an HTTP 200 and a list of dogs', () => {
let dogService: DogService;

// Create a 'pact' between the two applications in the integration we are testing
const provider = new Pact({
dir: path.resolve(process.cwd(), 'pacts'),
consumer: 'MyConsumer',
provider: 'MyProvider',
spec: SpecificationVersion.SPECIFICATION_VERSION_V4, // Modify this as needed for your use case
});
it('returns an HTTP 200 and a list of dogs', async () => {
const dogExample = { dog: 1 };
const EXPECTED_BODY = eachLike(dogExample);

// Arrange: Setup our expected interactions
//
// We use Pact to mock out the backend API
provider
await provider
.addInteraction()
.given('I have a list of dogs')
.uponReceiving('a request for all dogs with the builder pattern')
.withRequest('GET', '/dogs' (builder) => {
builder.query({ from: 'today' })
builder.headers({ Accept: 'application/json' })
.withRequest('GET', '/dogs', (builder) => {
builder.query({ from: 'today' });
builder.headers({ Accept: 'application/json' });
})
.willRespondWith(200, (builder) => {
builder.headers({ 'Content-Type': 'application/json' })
builder.jsonBody(EXPECTED_BODY)
builder.headers({ 'Content-Type': 'application/json' });
builder.jsonBody(EXPECTED_BODY);
})
.executeTest(async (mockserver) => {
// Act: test our API client behaves correctly
//
// Note we configure the DogService API client dynamically to
// point to the mock service Pact created for us, instead of
// the real one
dogService = new DogService({ url: mockserver.url });
const response = await dogService.getMeDogs('today');

// Assert: check the result
expect(response.data[0]).to.deep.eq(dogExample);
return response;
});

return provider.executeTest((mockserver) => {
// Act: test our API client behaves correctly
//
// Note we configure the DogService API client dynamically to
// point to the mock service Pact created for us, instead of
// the real one
dogService = new DogService(mockserver.url);
const response = await dogService.getMeDogs('today')

// Assert: check the result
expect(response.data[0]).to.deep.eq(dogExample);
});
});
});
```
Expand Down
5 changes: 3 additions & 2 deletions docs/graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ GraphQL is simply an abstraction over HTTP and may be tested via Pact.

| Role | Interface | Supported? |
|:---------:|:--------------------:|:----------:|
| Consumer | `Pact` | ✅ |
| Consumer | `Pact` / `PactV4` | ✅ |
| Consumer | `MessageConsumerPact` | ❌ |
| Consumer | `PactV3` | ❌ |
| Consumer | `PactV3` | ✅ |
| Consumer | `PactV4` | ✅ |
| Provider | `Verifier` | ✅ |
| Provider | `MessageProviderPact` | ❌ |

Expand Down
12 changes: 6 additions & 6 deletions docs/matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Matching rules

## V2 Matching rules

V2 only matching rules are found in the export `Matchers` of the `@pact-foundation/pact` package, and can be used with the `PactV2` dsl.
V2 only matching rules are found in the export `MatchersV2` of the `@pact-foundation/pact` package, and can be used with the `PactV2` dsl.

### Match common formats

Expand Down Expand Up @@ -44,7 +44,7 @@ Often times, you find yourself having to re-write regular expressions for common
### Match based on type

```javascript
const { like, string } = Matchers;
const { like, string } = MatchersV2;

provider.addInteraction({
state: 'Has some animals',
Expand Down Expand Up @@ -168,14 +168,14 @@ provider.addInteraction({
});
```

## V3 Matching rules
## V3 / V4 Matching rules

V3 only matching rules are found in the export `MatchersV3` of the `@pact-foundation/pact` package, and can be used with `PactV3` DSL.
V3/V4 only matching rules are found in the export `Matchers` of the `@pact-foundation/pact` package, and can be used with `Pact` / `PactV3` / `PactV4` DSL.

For example:

```javascript
const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
const { Pact, Matchers } = require('@pact-foundation/pact');
const {
eachLike,
atLeastLike,
Expand All @@ -185,7 +185,7 @@ const {
string,
regex,
like,
} = MatchersV3;
} = Matchers;

const animalBodyExpectation = {
id: integer(1),
Expand Down
105 changes: 69 additions & 36 deletions docs/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,52 +40,85 @@ From a Pact testing point of view, Pact takes the place of the intermediary (MQ/
The following test creates a contract for a Dog API handler:

```js
const path = require("path")
const {
MessageConsumerPact,
synchronousBodyHandler,
} = require("@pact-foundation/pact")
import {
Matchers,
v4SynchronousBodyHandler,
LogLevel,
Pact,
} from '@pact-foundation/pact';
const { like, regex } = Matchers;
const path = require('path');
const LOG_LEVEL = process.env.LOG_LEVEL || 'INFO';

// 1 Dog API Handler
const dogApiHandler = function (dog) {
if (!dog.id && !dog.name && !dog.type) {
throw new Error("missing fields")
export type Dog = {
id: string;
type: string;
name: string;
};

// This is your message handler function.
// It expects to receive a valid "dog" object
// and returns a failed promise if not
export function dogApiHandler(dog: Dog): void {
if (!dog.id || !dog.name || !dog.type) {
throw new Error('missing fields');
}

// do some other things to dog...
// e.g. dogRepository.save(dog)
return
return;
}

// 2 Pact Message Consumer
const messagePact = new MessageConsumerPact({
consumer: "MyJSMessageConsumer",
dir: path.resolve(process.cwd(), "pacts"),
pactfileWriteMode: "update",
provider: "MyJSMessageProvider",
})

describe("receive dog event", () => {
it("accepts a valid dog", () => {
// 3 Consumer expectations
return (
messagePact
.given("some state")
.expectsToReceive("a request for a dog")
.withContent({
id: like(1),
name: like("rover"),
type: term({ generate: "bulldog", matcher: "^(bulldog|sheepdog)$" }),
describe('Message consumer tests', () => {
// 2 Pact Message Consumer
const messagePact = new Pact({
consumer: 'MyJSMessageConsumerV4',
provider: 'MyJSMessageProviderV4',
logLevel: LOG_LEVEL as LogLevel,
});

describe('receive dog event', () => {
it('accepts a valid dog', () => {
// 3 Consumer expectations
return messagePact
.addAsynchronousInteraction()
.given('a dog named drover')
.expectsToReceive('a request for a dog', (builder: any) => {
builder
.withJSONContent({
id: like(1),
name: like('drover'),
type: regex('^(bulldog|sheepdog)$','bulldog'),
})
.withMetadata({
queue: 'animals',
});
})
.withMetadata({
"content-type": "application/json",
})

// 4 Verify consumers' ability to handle messages
.verify(synchronousBodyHandler(dogApiHandler))
)
})
})
.executeTest(v4SynchronousBodyHandler(dogApiHandler));
});
});

// This is an example of a pact breaking
// unskip to see how it works!
it.skip('Does not accept an invalid dog', () => {
return messagePact
.addAsynchronousInteraction()
.given('some state')
.expectsToReceive('a request for a dog', (builder: any) => {
builder
.withJSONContent({
name: like('fido'),
})
.withMetadata({
queue: 'animals',
});
})
.executeTest(v4SynchronousBodyHandler(dogApiHandler));
});
});

```

**Explanation**:
Expand Down
Loading