Skip to content

Commit cbec1b8

Browse files
authored
Merge pull request #214 from pact-foundation/pact-js-v3+master_merge
Pact js v3+master merge
2 parents 47344c1 + acd53f3 commit cbec1b8

File tree

10 files changed

+258
-25
lines changed

10 files changed

+258
-25
lines changed

.github/workflows/build-and-test.yml

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ jobs:
1010
runs-on: macos-latest
1111
strategy:
1212
matrix:
13-
node-version: [12.x, 14.x, 16.x]
13+
node-version: [14.x, 16.x, 18.x]
14+
fail-fast: false
1415

1516
steps:
1617
- uses: actions/checkout@v2
1718
- name: Use Node.js ${{ matrix.node-version }}
18-
uses: actions/setup-node@v1
19+
uses: actions/setup-node@v3
1920
with:
2021
node-version: ${{ matrix.node-version }}
2122
- run: scripts/ci/build-and-test.sh
@@ -32,14 +33,15 @@ jobs:
3233
runs-on: ubuntu-latest
3334
strategy:
3435
matrix:
35-
node-version: [12.x, 14.x, 16.x]
36+
node-version: [14.x, 16.x, 18.x]
37+
fail-fast: false
3638

3739
steps:
3840
- uses: actions/checkout@v2
39-
- name: Set MSVS version
40-
run: npm config set msvs_version 2017
41+
# - name: Set MSVS version
42+
# run: npm config set msvs_version 2017
4143
- name: Use Node.js ${{ matrix.node-version }}
42-
uses: actions/setup-node@v1
44+
uses: actions/setup-node@v3
4345
with:
4446
node-version: ${{ matrix.node-version }}
4547
- run: scripts/ci/build-and-test.sh
@@ -56,14 +58,20 @@ jobs:
5658
runs-on: windows-latest
5759
strategy:
5860
matrix:
59-
node-version: [12.x, 14.x, 16.x]
61+
node-version: [14.x, 16.x, 18.x]
62+
fail-fast: false
6063

6164
steps:
6265
- uses: actions/checkout@v2
6366
- name: Use Node.js ${{ matrix.node-version }}
64-
uses: actions/setup-node@v1
67+
uses: actions/setup-node@v3
6568
with:
6669
node-version: ${{ matrix.node-version }}
70+
- name: Fix node-gyp
71+
run: |-
72+
npm install --global node-gyp@latest
73+
npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}
74+
shell: pwsh
6775
- run: bash scripts/ci/build-and-test.sh
6876
shell: bash
6977
env:

README.md

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,15 @@
2727
## Adapter Installation
2828

2929
```
30-
# For pact @ 9.x
3130
npm install --save-dev jest-pact
3231
yarn add jest-pact --dev
33-
34-
# For pact @ 10.0.0-beta.x
35-
npm install --save-dev jest-pact@beta
36-
yarn add jest-pact@beta --dev
3732
```
3833

3934
If you have more than one file with pact tests for the same consumer/provider
4035
pair, you will also need to add `--runInBand` to your `jest` or `react-scripts test` command in your package.json. This avoids race conditions with the mock
4136
server writing to the pact file.
4237

43-
## Usage
38+
## Usage - Pact-JS V2
4439

4540
Say that your API layer looks something like this:
4641

@@ -113,9 +108,49 @@ pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, provider => {
113108
});
114109
```
115110
116-
## V3 Pact spec with 10.0.0-beta.x
111+
## Usage - Pact-JS V3
112+
113+
We also include a wrapper for Pact-JS V3.
114+
115+
**Note: The API is NOT finalised. Feedback welcome**
116+
117+
If you have thoughts or feedback about the DSL, please let us know via slack or open issue.
118+
119+
Currently, only a default for the pact directory is provided by the jest-pact wrapper `jest-pact/v3`.
120+
121+
```js
122+
import { pactWith } from 'jest-pact/v3';
123+
import { MatchersV3 } from '@pact-foundation/pact';
124+
import api from 'yourCode';
125+
126+
pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, (interaction) => {
127+
interaction('A request for API health', ({ provider, execute }) => {
128+
beforeEach(() =>
129+
provider
130+
.given('Server is healthy')
131+
.uponReceiving('A request for API health')
132+
.withRequest({
133+
method: 'GET',
134+
path: '/health',
135+
})
136+
.willRespondWith({
137+
status: 200,
138+
body: {
139+
status: MatchersV3.like('up'),
140+
},
141+
})
142+
);
117143

118-
See [the usage instructions here](https://github.com/pact-foundation/jest-pact/blob/pact-js-v3/README.md)
144+
execute('some api call', (mockserver) =>
145+
api(mockserver.url)
146+
.health()
147+
.then((health) => {
148+
expect(health).toEqual('up');
149+
})
150+
);
151+
});
152+
});
153+
```
119154
120155
# Best practices
121156

RELEASING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,4 @@ git push origin master --follow-tags
8585

8686
Depending on the nature of the new commits to master after the release, you
8787
may need to rebase them on top of the tagged release commit and force push (only do this
88-
if the released version would be different to the version tagged by `npm run release`)
88+
if the released version would be different to the version tagged by `npm run release`)

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jest-pact",
3-
"version": "0.9.4",
3+
"version": "0.10.0",
44
"description": "a pact adaptor for jest",
55
"main": "dist/index.js",
66
"scripts": {
@@ -39,7 +39,7 @@
3939
"devDependencies": {
4040
"@commitlint/cli": "^10.0.0",
4141
"@commitlint/config-conventional": "^10.0.0",
42-
"@pact-foundation/pact": "9.17.3",
42+
"@pact-foundation/pact": "^10.0.2",
4343
"@pact-foundation/pact-js-prettier-config": "^1.0.0",
4444
"@types/jest": "^27.1.3",
4545
"@types/supertest": "2.0.8",
@@ -60,7 +60,7 @@
6060
"typescript": "4.6.4"
6161
},
6262
"peerDependencies": {
63-
"@pact-foundation/pact": "^9.12.2",
63+
"@pact-foundation/pact": "^v10.0.0-beta.61 || ^10.0.2",
6464
"jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0"
6565
},
6666
"lint-staged": {

src/test/fpactwith.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { Pact } from '@pact-foundation/pact';
22
import { fpactWith } from '../index';
3+
import { getClient, postValidRequest } from './pactwith.test';
34

45
describe('fpactwith', () => {
56
fpactWith(
67
{ consumer: 'MyConsumer', provider: 'NoProvider' },
78
(provider: Pact) => {
8-
it('should only run this test', () => {});
9+
beforeEach(() => provider.addInteraction(postValidRequest));
10+
11+
it('should only run this test', () =>
12+
getClient(provider)
13+
.get('/v2/pet/1845563262948980200')
14+
.set('api_key', '[]')
15+
.expect(200));
916
}
1017
);
1118

src/test/messagePactWith.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
MessageConsumerPact,
44
synchronousBodyHandler,
55
} from '@pact-foundation/pact';
6+
import { AnyJson } from '@pact-foundation/pact/src/common/jsonTypes';
67
import { messagePactWith } from '../index';
78

89
interface Dog {
@@ -35,7 +36,11 @@ const arbitraryPact = (provider: MessageConsumerPact) => {
3536
.withMetadata({
3637
'content-type': 'application/json',
3738
})
38-
.verify(synchronousBodyHandler(dogApiHandler));
39+
.verify(
40+
synchronousBodyHandler(
41+
(dogApiHandler as unknown) as (body: AnyJson | Buffer) => void
42+
)
43+
);
3944
});
4045
});
4146
};

src/test/pactwith.only.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { Pact } from '@pact-foundation/pact';
22
import { pactWith } from '../index';
3+
import { getClient, postValidRequest } from './pactwith.test';
34

45
describe('pactwith.only', () => {
56
pactWith.only(
67
{ consumer: 'MyConsumer', provider: 'NoProvider' },
78
(provider: Pact) => {
8-
it('should only run this test', () => {});
9+
beforeEach(() => provider.addInteraction(postValidRequest));
10+
it('should only run this test', () =>
11+
getClient(provider)
12+
.get('/v2/pet/1845563262948980200')
13+
.set('api_key', '[]')
14+
.expect(200));
915
}
1016
);
1117

src/test/pactwith.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { InteractionObject, Pact } from '@pact-foundation/pact';
22
import * as supertest from 'supertest';
33
import { getProviderBaseUrl, pactWith } from '../index';
44

5-
const getClient = (provider: Pact) => supertest(provider.mockService.baseUrl);
5+
export const getClient = (provider: Pact) =>
6+
supertest(provider.mockService.baseUrl);
67
const pactPort: number = 5001;
78

8-
const postValidRequest: InteractionObject = {
9+
export const postValidRequest: InteractionObject = {
910
state: 'A pet 1845563262948980200 exists',
1011
uponReceiving: 'A get request to get a pet 1845563262948980200',
1112
willRespondWith: {
@@ -32,15 +33,25 @@ pactWith(
3233
});
3334

3435
describe('provider object', () => {
36+
beforeEach(() => provider.addInteraction(postValidRequest));
37+
3538
test('should show the specified port in the URL', () => {
3639
expect(provider.mockService.baseUrl).toMatch(
3740
new RegExp(`${pactPort}$`)
3841
);
42+
return getClient(provider)
43+
.get('/v2/pet/1845563262948980200')
44+
.set('api_key', '[]')
45+
.expect(200);
3946
});
4047
test('should return the port on getProviderBaseUrl', () => {
4148
expect(getProviderBaseUrl(provider)).toEqual(
4249
`http://127.0.0.1:${pactPort}`
4350
);
51+
return getClient(provider)
52+
.get('/v2/pet/1845563262948980200')
53+
.set('api_key', '[]')
54+
.expect(200);
4455
});
4556
});
4657
}
@@ -60,14 +71,24 @@ pactWith(
6071
});
6172

6273
describe('provider object', () => {
74+
beforeEach(() => provider.addInteraction(postValidRequest));
75+
6376
test('should show the randomly assigned port in the URL', () => {
6477
expect(provider.mockService.baseUrl).toMatch(new RegExp(`\\d{4,5}$`));
78+
return getClient(provider)
79+
.get('/v2/pet/1845563262948980200')
80+
.set('api_key', '[]')
81+
.expect(200);
6582
});
6683

6784
test('should return the host on getProviderBaseUrl', () => {
6885
expect(getProviderBaseUrl(provider)).toMatch(
6986
new RegExp('^http://127.0.0.1:\\d{4,5}$')
7087
);
88+
return getClient(provider)
89+
.get('/v2/pet/1845563262948980200')
90+
.set('api_key', '[]')
91+
.expect(200);
7192
});
7293
});
7394
}

src/v3/index.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as pactV3 from '@pact-foundation/pact';
2+
import * as path from 'path';
3+
4+
export type JestPactOptionsV3 = Omit<pactV3.PactV3Options, 'dir'> & {
5+
dir?: string;
6+
timeout?: number;
7+
};
8+
9+
type ExecuteTestFn = (mockServer: pactV3.V3MockServer) => Promise<unknown>;
10+
11+
interface DescibeArg {
12+
provider: pactV3.PactV3;
13+
execute: JestPactExecuteTestFn;
14+
}
15+
16+
type JestPactExecuteTestFn = (description: string, fn: ExecuteTestFn) => void;
17+
18+
export type JestProvidedPactFnV3 = (D: DescibeArg) => void;
19+
export type JestDescribePactFnV3 = (
20+
description: string,
21+
fn: JestProvidedPactFnV3
22+
) => void;
23+
24+
export type JestProvidedDescribeFnV3 = (
25+
pactDescribe: JestDescribePactFnV3
26+
) => void;
27+
28+
const applyDefaults = (options: JestPactOptionsV3): pactV3.PactV3Options => ({
29+
dir: path.resolve(process.cwd(), 'pact/pacts'),
30+
...options,
31+
});
32+
33+
const setupProvider = (options: pactV3.PactV3Options) => {
34+
const pactDescribe: JestDescribePactFnV3 = (
35+
describeDescription: string,
36+
fn: JestProvidedPactFnV3
37+
) => {
38+
describe(describeDescription, () => {
39+
const provider = new pactV3.PactV3(options);
40+
const execute = (testDescription: string, executeTest: ExecuteTestFn) => {
41+
it(testDescription, () => provider.executeTest(executeTest));
42+
};
43+
fn({ provider, execute });
44+
});
45+
};
46+
return pactDescribe;
47+
};
48+
49+
const jestPactWrapper = (
50+
options: JestPactOptionsV3,
51+
tests: JestProvidedDescribeFnV3
52+
): void => {
53+
const pactTestTimeout = options.timeout || 30000;
54+
55+
describe(`with ${pactTestTimeout} ms timeout for Pact`, () => {
56+
let originalTimeout: number;
57+
58+
beforeAll(() => {
59+
// Jest's default timeout is 5000, and jest doesn't provide a way of
60+
// asking what the current timeout is. In Jest 24 and 25, Jasmine was probably
61+
// the test runner, so we can ask Jasmine if it is there. In later versions of
62+
// Jest (eg 26 and up), Jasmine may not be defined.
63+
// See https://github.com/pact-foundation/jest-pact/issues/197 for discussion
64+
//
65+
// For now, we just assume that 5000 was the original timeout.
66+
// The impact is likely to be small, as `jest.setTimeout()` only works for the
67+
// current test file
68+
originalTimeout = global.jasmine
69+
? global.jasmine.DEFAULT_TIMEOUT_INTERVAL
70+
: 5000;
71+
jest.setTimeout(pactTestTimeout);
72+
});
73+
74+
afterAll(() => {
75+
jest.setTimeout(originalTimeout);
76+
});
77+
78+
tests(setupProvider(applyDefaults(options)));
79+
});
80+
};
81+
82+
const describeString = (options: JestPactOptionsV3) =>
83+
`Pact between ${options.consumer} and ${options.provider}`;
84+
85+
export const pactWith = (
86+
options: JestPactOptionsV3,
87+
tests: JestProvidedDescribeFnV3
88+
) => describe(describeString(options), () => jestPactWrapper(options, tests));
89+
90+
export const xpactWith = (
91+
options: JestPactOptionsV3,
92+
tests: JestProvidedDescribeFnV3
93+
) => xdescribe(describeString(options), () => jestPactWrapper(options, tests));
94+
95+
export const fpactWith = (
96+
options: JestPactOptionsV3,
97+
tests: JestProvidedDescribeFnV3
98+
) => fdescribe(describeString(options), () => jestPactWrapper(options, tests));

0 commit comments

Comments
 (0)