@pact-foundation/pact 7.3.0 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/CONTRIBUTING.md +22 -9
- package/LICENSE +1 -2
- package/README.md +253 -157
- package/common/logger.js +10 -5
- package/common/logger.js.map +1 -1
- package/common/metadata.js.map +1 -1
- package/common/net.js +3 -2
- package/common/net.js.map +1 -1
- package/common/net.spec.d.ts +1 -0
- package/common/net.spec.js +47 -0
- package/common/net.spec.js.map +1 -0
- package/common/request.js +1 -2
- package/common/request.js.map +1 -1
- package/common/request.spec.d.ts +1 -0
- package/common/request.spec.js +71 -0
- package/common/request.spec.js.map +1 -0
- package/common/utils.js +1 -2
- package/common/utils.js.map +1 -1
- package/dsl/apolloGraphql.d.ts +1 -1
- package/dsl/apolloGraphql.js +6 -3
- package/dsl/apolloGraphql.js.map +1 -1
- package/dsl/apolloGraphql.spec.d.ts +1 -0
- package/dsl/apolloGraphql.spec.js +47 -0
- package/dsl/apolloGraphql.spec.js.map +1 -0
- package/dsl/graphql.js +19 -9
- package/dsl/graphql.js.map +1 -1
- package/dsl/graphql.spec.d.ts +1 -0
- package/dsl/graphql.spec.js +141 -0
- package/dsl/graphql.spec.js.map +1 -0
- package/dsl/interaction.js +9 -7
- package/dsl/interaction.js.map +1 -1
- package/dsl/interaction.spec.d.ts +1 -0
- package/dsl/interaction.spec.js +172 -0
- package/dsl/interaction.spec.js.map +1 -0
- package/dsl/matchers.d.ts +25 -25
- package/dsl/matchers.js +57 -55
- package/dsl/matchers.js.map +1 -1
- package/dsl/matchers.spec.d.ts +1 -0
- package/dsl/matchers.spec.js +537 -0
- package/dsl/matchers.spec.js.map +1 -0
- package/dsl/message.d.ts +29 -3
- package/dsl/mockService.d.ts +6 -0
- package/dsl/mockService.js +10 -4
- package/dsl/mockService.js.map +1 -1
- package/dsl/mockService.spec.d.ts +1 -0
- package/dsl/mockService.spec.js +126 -0
- package/dsl/mockService.spec.js.map +1 -0
- package/dsl/verifier.d.ts +48 -2
- package/dsl/verifier.js +142 -3
- package/dsl/verifier.js.map +1 -1
- package/dsl/verifier.spec.d.ts +1 -0
- package/dsl/verifier.spec.js +299 -0
- package/dsl/verifier.spec.js.map +1 -0
- package/errors/configurationError.d.ts +2 -0
- package/errors/configurationError.js +24 -0
- package/errors/configurationError.js.map +1 -0
- package/errors/graphQLQueryError.d.ts +2 -0
- package/errors/graphQLQueryError.js +24 -0
- package/errors/graphQLQueryError.js.map +1 -0
- package/errors/matcherError.d.ts +2 -0
- package/errors/matcherError.js +24 -0
- package/errors/matcherError.js.map +1 -0
- package/errors/verificationError.d.ts +2 -0
- package/errors/verificationError.js +24 -0
- package/errors/verificationError.js.map +1 -0
- package/httpPact.d.ts +69 -0
- package/httpPact.js +206 -0
- package/httpPact.js.map +1 -0
- package/httpPact.spec.d.ts +1 -0
- package/httpPact.spec.js +337 -0
- package/httpPact.spec.js.map +1 -0
- package/messageConsumerPact.d.ts +3 -3
- package/messageConsumerPact.js +5 -4
- package/messageConsumerPact.js.map +1 -1
- package/messageConsumerPact.spec.d.ts +1 -0
- package/messageConsumerPact.spec.js +161 -0
- package/messageConsumerPact.spec.js.map +1 -0
- package/messageProviderPact.d.ts +0 -1
- package/messageProviderPact.js +11 -11
- package/messageProviderPact.js.map +1 -1
- package/messageProviderPact.spec.d.ts +1 -0
- package/messageProviderPact.spec.js +146 -0
- package/messageProviderPact.spec.js.map +1 -0
- package/package.json +24 -26
- package/pact-web.d.ts +2 -2
- package/pact-web.js +20 -10
- package/pact-web.js.map +1 -1
- package/pact-web.spec.d.ts +1 -0
- package/pact-web.spec.js +205 -0
- package/pact-web.spec.js.map +1 -0
- package/pact.d.ts +28 -65
- package/pact.integration.spec.d.ts +0 -0
- package/pact.integration.spec.js +292 -0
- package/pact.integration.spec.js.map +1 -0
- package/pact.js +21 -189
- package/pact.js.map +1 -1
package/README.md
CHANGED
|
@@ -33,16 +33,16 @@ Read [Getting started with Pact] for more information for beginners.
|
|
|
33
33
|
|
|
34
34
|
- [Pact JS](#pact-js)
|
|
35
35
|
- [Installation](#installation)
|
|
36
|
+
- [Do Not Track](#do-not-track)
|
|
36
37
|
- [Using Pact JS](#using-pact-js)
|
|
37
38
|
- [HTTP API Testing](#http-api-testing)
|
|
38
39
|
- [Consumer Side Testing](#consumer-side-testing)
|
|
39
40
|
- [API](#api)
|
|
40
|
-
- [Constructor Options](#constructor-options)
|
|
41
41
|
- [Example](#example)
|
|
42
42
|
- [Provider API Testing](#provider-api-testing)
|
|
43
43
|
- [Verification Options](#verification-options)
|
|
44
44
|
- [API with Provider States](#api-with-provider-states)
|
|
45
|
-
- [
|
|
45
|
+
- [Modify Requests Prior to Verification (Request Filters)](#modify-requests-prior-to-verification-request-filters)
|
|
46
46
|
- [Publishing Pacts to a Broker](#publishing-pacts-to-a-broker)
|
|
47
47
|
- [Publishing options](#publishing-options)
|
|
48
48
|
- [Publishing Verification Results to a Pact Broker](#publishing-verification-results-to-a-pact-broker)
|
|
@@ -81,6 +81,21 @@ Read [Getting started with Pact] for more information for beginners.
|
|
|
81
81
|
npm i -S @pact-foundation/pact@latest
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### Do Not Track
|
|
85
|
+
|
|
86
|
+
In order to get better statistics as to who is using Pact, we have an anonymous tracking event that triggers when Pact installs for the first time. The only things we [track](https://github.com/pact-foundation/pact-node/blob/master/standalone/install.ts#L132-L143) are your type of OS, and the version information for the package being installed. No PII data is sent as part of this request. To respect your privacy, you can disable tracking by simply adding a 'do not track' flag within your package.json file or setting the environment variable `PACT_DO_NOT_TRACK=1`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"name": "some-project",
|
|
91
|
+
...
|
|
92
|
+
"config": {
|
|
93
|
+
"pact_do_not_track": true
|
|
94
|
+
},
|
|
95
|
+
...
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
84
99
|
See the [Changelog] for versions and their history.
|
|
85
100
|
|
|
86
101
|
## Using Pact JS
|
|
@@ -94,13 +109,15 @@ Pact supports [synchronous request-response style HTTP interactions](#http-api-t
|
|
|
94
109
|
To use the library on your tests, add the pact dependency:
|
|
95
110
|
|
|
96
111
|
```javascript
|
|
97
|
-
const { Pact } = require("pact")
|
|
112
|
+
const { Pact } = require("pact")
|
|
98
113
|
```
|
|
99
114
|
|
|
100
115
|
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:
|
|
101
116
|
|
|
102
117
|
#### API
|
|
103
118
|
|
|
119
|
+
<details><summary>Consumer API</summary>
|
|
120
|
+
|
|
104
121
|
| API | Options | Returns | Description |
|
|
105
122
|
| ------------------- | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
106
123
|
| `new Pact(options)` | See constructor options below | `Object` | Creates a Mock Server test double of your Provider API. If you need multiple Providers for a scenario, you can create as many as these as you need. |
|
|
@@ -109,7 +126,9 @@ The `Pact` class provides the following high-level APIs, they are listed in the
|
|
|
109
126
|
| `verify()` | n/a | `Promise` | Verifies that all interactions specified. This should be called once per test, to ensure your expectations were correct |
|
|
110
127
|
| `finalize()` | n/a | `Promise` | Records the interactions registered to the Mock Server into the pact file and shuts it down. You would normally call this only once in an `afterAll(...)` type clause. |
|
|
111
128
|
|
|
112
|
-
|
|
129
|
+
</details>
|
|
130
|
+
|
|
131
|
+
<details><summary>Constructor</summary>
|
|
113
132
|
|
|
114
133
|
| Parameter | Required? | Type | Description |
|
|
115
134
|
| ------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------- |
|
|
@@ -127,6 +146,8 @@ The `Pact` class provides the following high-level APIs, they are listed in the
|
|
|
127
146
|
| `cors` | no | boolean | Allow CORS OPTION requests to be accepted, defaults to false |
|
|
128
147
|
| `pactfileWriteMode` | no | string | Control how the Pact files are written. Choices: 'overwrite' 'update' or 'none'. Defaults to 'overwrite' |
|
|
129
148
|
|
|
149
|
+
</details>
|
|
150
|
+
|
|
130
151
|
#### Example
|
|
131
152
|
|
|
132
153
|
The first step is to create a test for your API Consumer. The example below uses [Mocha](https://mochajs.org), and demonstrates the basic approach:
|
|
@@ -141,18 +162,15 @@ The first step is to create a test for your API Consumer. The example below uses
|
|
|
141
162
|
Check out the `examples` folder for examples with Karma Jasmine, Mocha and Jest. The example below is taken from the [integration spec](https://github.com/pact-foundation/pact-js/blob/master/src/pact.integration.spec.ts).
|
|
142
163
|
|
|
143
164
|
```javascript
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const chai = require('chai')
|
|
149
|
-
const { Pact } = require('@pact-foundation/pact')
|
|
150
|
-
const chaiAsPromised = require('chai-as-promised')
|
|
165
|
+
const path = require("path")
|
|
166
|
+
const chai = require("chai")
|
|
167
|
+
const { Pact } = require("@pact-foundation/pact")
|
|
168
|
+
const chaiAsPromised = require("chai-as-promised")
|
|
151
169
|
|
|
152
|
-
const expect = chai.expect
|
|
153
|
-
const MOCK_SERVER_PORT = 2202
|
|
170
|
+
const expect = chai.expect
|
|
171
|
+
const MOCK_SERVER_PORT = 2202
|
|
154
172
|
|
|
155
|
-
chai.use(chaiAsPromised)
|
|
173
|
+
chai.use(chaiAsPromised)
|
|
156
174
|
|
|
157
175
|
describe("Pact", () => {
|
|
158
176
|
// (1) Create the Pact object to represent your provider
|
|
@@ -163,8 +181,8 @@ describe("Pact", () => {
|
|
|
163
181
|
log: path.resolve(process.cwd(), "logs", "pact.log"),
|
|
164
182
|
dir: path.resolve(process.cwd(), "pacts"),
|
|
165
183
|
logLevel: "INFO",
|
|
166
|
-
spec: 2
|
|
167
|
-
})
|
|
184
|
+
spec: 2,
|
|
185
|
+
})
|
|
168
186
|
|
|
169
187
|
// this is the response you expect from your Provider
|
|
170
188
|
const EXPECTED_BODY = [
|
|
@@ -176,10 +194,10 @@ describe("Pact", () => {
|
|
|
176
194
|
{ id: 1, name: "Do the laundry", done: true },
|
|
177
195
|
{ id: 2, name: "Do the dishes", done: false },
|
|
178
196
|
{ id: 3, name: "Do the backyard", done: false },
|
|
179
|
-
{ id: 4, name: "Do nothing", done: false }
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
]
|
|
197
|
+
{ id: 4, name: "Do nothing", done: false },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
]
|
|
183
201
|
|
|
184
202
|
context("when there are a list of projects", () => {
|
|
185
203
|
describe("and there is a valid user session", () => {
|
|
@@ -196,52 +214,56 @@ describe("Pact", () => {
|
|
|
196
214
|
withRequest: {
|
|
197
215
|
method: "GET",
|
|
198
216
|
path: "/projects",
|
|
199
|
-
headers: { Accept: "application/json" }
|
|
217
|
+
headers: { Accept: "application/json" },
|
|
200
218
|
},
|
|
201
219
|
willRespondWith: {
|
|
202
220
|
status: 200,
|
|
203
221
|
headers: { "Content-Type": "application/json" },
|
|
204
|
-
body: EXPECTED_BODY
|
|
205
|
-
}
|
|
206
|
-
})
|
|
222
|
+
body: EXPECTED_BODY,
|
|
223
|
+
},
|
|
224
|
+
})
|
|
207
225
|
})
|
|
208
|
-
.then(() => done())
|
|
209
|
-
})
|
|
226
|
+
.then(() => done())
|
|
227
|
+
})
|
|
210
228
|
|
|
211
229
|
// (4) write your test(s)
|
|
212
|
-
it("
|
|
213
|
-
const todoApp = new TodoApp()
|
|
230
|
+
it("generates a list of TODOs for the main screen", () => {
|
|
231
|
+
const todoApp = new TodoApp()
|
|
214
232
|
todoApp
|
|
215
233
|
.getProjects() // <- this method would make the remote http call
|
|
216
234
|
.then(projects => {
|
|
217
|
-
expect(projects).to.be.a("array")
|
|
218
|
-
expect(projects).to.have.deep.property("projects[0].id", 1)
|
|
235
|
+
expect(projects).to.be.a("array")
|
|
236
|
+
expect(projects).to.have.deep.property("projects[0].id", 1)
|
|
219
237
|
|
|
220
238
|
// (5) validate the interactions you've registered and expected occurred
|
|
221
239
|
// this will throw an error if it fails telling you what went wrong
|
|
222
|
-
expect(provider.verify()).to.not.throw()
|
|
223
|
-
})
|
|
224
|
-
})
|
|
240
|
+
expect(provider.verify()).to.not.throw()
|
|
241
|
+
})
|
|
242
|
+
})
|
|
225
243
|
|
|
226
244
|
// (6) write the pact file for this consumer-provider pair,
|
|
227
245
|
// and shutdown the associated mock server.
|
|
228
246
|
// You should do this only _once_ per Provider you are testing.
|
|
229
247
|
after(() => {
|
|
230
|
-
return provider.finalize()
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
})
|
|
248
|
+
return provider.finalize()
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
235
253
|
```
|
|
236
254
|
|
|
237
255
|
### Provider API Testing
|
|
238
256
|
|
|
257
|
+
<details><summary>Provider API</summary>
|
|
258
|
+
|
|
239
259
|
Once you have created Pacts for your Consumer, you need to validate those Pacts against your Provider. The Verifier object provides the following API for you to do so:
|
|
240
260
|
|
|
241
261
|
| API | Options | Returns | Description |
|
|
242
262
|
| ------------------ | :-------: | --------- | --------------------- |
|
|
243
263
|
| `verifyProvider()` | See below | `Promise` | Start the Mock Server |
|
|
244
264
|
|
|
265
|
+
</details>
|
|
266
|
+
|
|
245
267
|
1. Start your local Provider service.
|
|
246
268
|
1. Optionally, instrument your API with ability to configure [provider states](https://github.com/pact-foundation/pact-provider-verifier/)
|
|
247
269
|
1. Then run the Provider side verification step
|
|
@@ -259,47 +281,102 @@ new Verifier().verifyProvider(opts).then(function () {
|
|
|
259
281
|
|
|
260
282
|
#### Verification Options
|
|
261
283
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
<details><summary>Verification Options</summary>
|
|
285
|
+
|
|
286
|
+
| Parameter | Required | Type | Description |
|
|
287
|
+
| --------------------------- | :------: | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
288
|
+
| `providerBaseUrl` | true | string | Running API provider host endpoint. Required. |
|
|
289
|
+
| `provider` | true | string | Name of the Provider. Required. |
|
|
290
|
+
| `pactUrls` | true | array of strings | Array of local Pact file paths or HTTP-based URLs (e.g. from a broker). Required if not using a Broker. |
|
|
291
|
+
| `pactBrokerUrl` | false | string | URL of the Pact Broker to retrieve pacts from. Required if not using pactUrls. |
|
|
292
|
+
| `tags` | false | array of strings | Array of tags, used to filter pacts from the Broker. |
|
|
293
|
+
| `providerStatesSetupUrl` | false | string | DEPRECATED (see `stateHandlers`). URL to call with a POST request for each `providerState` defined in a pact (see below for more info). |
|
|
294
|
+
| `pactBrokerUsername` | false | string | Username for Pact Broker basic authentication |
|
|
295
|
+
| `pactBrokerPassword` | false | string | Password for Pact Broker basic authentication |
|
|
296
|
+
| `publishVerificationResult` | false | boolean | Publish verification result to Broker | boolean |
|
|
297
|
+
| `providerVersion` | false | string | Provider version, required to publish verification results to a broker |
|
|
298
|
+
| `customProviderHeaders` | false | array of strings | Header(s) to add to any requests to the provider service. eg `Authorization: Basic cGFjdDpwYWN0`. All interactions will receive the header. See `requestFilter` for when more flexiblility is required in modifying the request to the provider. |
|
|
299
|
+
| `timeout` | false | number | The duration in ms we should wait to confirm verification process was successful. Defaults to 30000. |
|
|
300
|
+
| `requestFilter` | false | object | An Express middleware handler (See https://expressjs.com/en/guide/writing-middleware.html) to modify requests and responses from the provider. See below for more details. |
|
|
301
|
+
| `stateHandlers` | false | object | Provider state handlers. A map of `string` -> `() => Promise`, where each string is the state to setup, and the function is used to configure the state in the Provider. See below for detail. |
|
|
302
|
+
|
|
303
|
+
</details>
|
|
304
|
+
|
|
305
|
+
Read more about [Verifying Pacts](https://docs.pact.io/getting_started/verifying_pacts).
|
|
278
306
|
|
|
279
307
|
#### API with Provider States
|
|
280
308
|
|
|
281
|
-
If you have defined any `state`s in your consumer tests, the `Verifier` can put the provider into the right state
|
|
309
|
+
If you have defined any `state`s in your consumer tests, the `Verifier` can put the provider into the right state prior to sending the request. For example, the provider can use the state to mock away certain database queries. To support this, set up a handler for each `state` using hooks on the `stateHandlers` property. Here is an example from our [e2e suite](https://github.com/pact-foundation/pact-js/blob/master/examples/e2e/test/provider.spec.js):
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
let opts = {
|
|
313
|
+
...
|
|
314
|
+
stateHandlers: {
|
|
315
|
+
"Has no animals": () => {
|
|
316
|
+
animalRepository.clear()
|
|
317
|
+
return Promise.resolve(`Animals removed from the db`)
|
|
318
|
+
},
|
|
319
|
+
"Has some animals": () => {
|
|
320
|
+
importData()
|
|
321
|
+
return Promise.resolve(`Animals added to the db`)
|
|
322
|
+
},
|
|
323
|
+
"Has an animal with ID 1": () => {
|
|
324
|
+
importData()
|
|
325
|
+
return Promise.resolve(`Animals added to the db`)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return new Verifier(opts).verifyProvider().then(...)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
As you can see, for each state ("Has no animals", ...), we configure the local datastore differently. If this option is not configured, the `Verifier` will ignore the provider states defined in the pact and log a warning.
|
|
334
|
+
|
|
335
|
+
Read more about [Provider States](https://docs.pact.io/getting_started/provider_states).
|
|
282
336
|
|
|
283
|
-
|
|
337
|
+
#### Modify Requests Prior to Verification (Request Filters)
|
|
284
338
|
|
|
285
|
-
|
|
339
|
+
Sometimes you may need to add things to the requests that can't be persisted in a pact file. Examples of these are authentication tokens with a small life span. e.g. an OAuth bearer token: `Authorization: Bearer 0b79bab50daca910b000d4f1a2b675d604257e42`.
|
|
286
340
|
|
|
287
|
-
|
|
341
|
+
For these cases, we have two facilities that should be carefully used during verification:
|
|
288
342
|
|
|
289
|
-
|
|
343
|
+
1. the ability to specify custom headers to be sent during provider verification. The flag to achieve this is `customProviderHeaders`.
|
|
344
|
+
1. the ability to modify a request/response and modify the payload. The flag to achieve this is `requestFilter`.
|
|
290
345
|
|
|
291
|
-
|
|
346
|
+
**Example API with Authorization**
|
|
292
347
|
|
|
293
|
-
For example, to have
|
|
348
|
+
For example, to have an `Authorization` bearer token header sent as part of the verification request, set the `verifyProvider` options as per below:
|
|
294
349
|
|
|
295
350
|
```js
|
|
351
|
+
let token
|
|
296
352
|
let opts = {
|
|
297
353
|
provider: 'Animal Profile Service',
|
|
298
354
|
...
|
|
299
|
-
|
|
355
|
+
stateHandlers: {
|
|
356
|
+
"is authenticated": () => {
|
|
357
|
+
token = "1234"
|
|
358
|
+
Promise.resolve(`Valid bearer token generated`)
|
|
359
|
+
},
|
|
360
|
+
"is not authenticated": () => {
|
|
361
|
+
token = ""
|
|
362
|
+
Promise.resolve(`Expired bearer token generated`)
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// this middleware is executed for each request, allowing `token` to change between invocations
|
|
367
|
+
// it is common to pair this with `stateHandlers` as per above, that can set/expire the token
|
|
368
|
+
// for different test cases
|
|
369
|
+
requestFilter: (req, res, next) => {
|
|
370
|
+
req.headers["Authorization"] = `Bearer: ${token}`
|
|
371
|
+
next()
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// This header will always be sent for each and every request, and can't be dynamic
|
|
375
|
+
// (i.e. passing a variable instead of the bearer token)
|
|
376
|
+
customProviderHeaders: ["Authorization: Bearer 1234"]
|
|
300
377
|
}
|
|
301
378
|
|
|
302
|
-
return new Verifier().verifyProvider(
|
|
379
|
+
return new Verifier(opts).verifyProvider().then(...)
|
|
303
380
|
```
|
|
304
381
|
|
|
305
382
|
As you can see, this is your opportunity to modify\add to headers being sent to the Provider API, for example to create a valid time-bound token.
|
|
@@ -312,13 +389,13 @@ Sharing is caring - to simplify sharing Pacts between Consumers and Providers, w
|
|
|
312
389
|
|
|
313
390
|
The Broker:
|
|
314
391
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
392
|
+
- versions your contracts
|
|
393
|
+
- tells you which versions of your applications can be deployed safely together
|
|
394
|
+
- allows you to deploy your services independently
|
|
395
|
+
- provides API documentation of your applications that is guaranteed to be up-to date
|
|
396
|
+
- visualises the relationships between your services
|
|
397
|
+
- integrates with other systems, such as Slack or your CI server, via webhooks
|
|
398
|
+
- ...and much much [more](https://docs.pact.io/getting_started/sharing_pacts).
|
|
322
399
|
|
|
323
400
|
[Host your own](https://github.com/pact-foundation/pact_broker), or signup for a free hosted [Pact Broker](https://pact.dius.com.au).
|
|
324
401
|
|
|
@@ -335,15 +412,19 @@ pact.publishPacts(opts)).then(function () {
|
|
|
335
412
|
|
|
336
413
|
#### Publishing options
|
|
337
414
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
|
341
|
-
|
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
415
|
+
<details><summary>Publishing Options</summary>
|
|
416
|
+
|
|
417
|
+
| Parameter | Required | Type | Description |
|
|
418
|
+
| -------------------- | :------: | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
419
|
+
| `providerBaseUrl` | `false` | string | Running API provider host endpoint. |
|
|
420
|
+
| `pactFilesOrDirs` | `true` | array of strings | Array of local Pact files or directories containing pact files. Path must be absolute. Required. |
|
|
421
|
+
| `pactBroker` | `true` | string | The base URL of the Pact Broker. eg. https://test.pact.dius.com.au. Required. |
|
|
422
|
+
| `pactBrokerUsername` | `false` | string | Username for Pact Broker basic authentication. Optional |
|
|
423
|
+
| `pactBrokerPassword` | `false` | string | Password for Pact Broker basic authentication. Optional |
|
|
424
|
+
| `consumerVersion` | `true` | string | The consumer application version; e.g. '1.0.0-cac389f'. ([See more info on versioning](https://docs.pact.io/getting_started/versioning_in_the_pact_broker)) | |
|
|
425
|
+
| `tags` | `false` | array of strings | Tag your pacts, often used with your branching, release or environment strategy e.g. ['prod', 'test'] |
|
|
426
|
+
|
|
427
|
+
</details>
|
|
347
428
|
|
|
348
429
|
#### Publishing Verification Results to a Pact Broker
|
|
349
430
|
|
|
@@ -393,18 +474,22 @@ From a Pact testing point of view, Pact takes the place of the intermediary (MQ/
|
|
|
393
474
|
The following test creates a contract for a Dog API handler:
|
|
394
475
|
|
|
395
476
|
```js
|
|
396
|
-
const {
|
|
477
|
+
const {
|
|
478
|
+
MessageConsumerPact,
|
|
479
|
+
Message,
|
|
480
|
+
synchronousBodyHandler,
|
|
481
|
+
} = require("@pact-foundation/pact")
|
|
397
482
|
|
|
398
483
|
// 1 Dog API Handler
|
|
399
484
|
const dogApiHandler = function(dog) {
|
|
400
485
|
if (!dog.id && !dog.name && !dog.type) {
|
|
401
|
-
throw new Error("missing fields")
|
|
486
|
+
throw new Error("missing fields")
|
|
402
487
|
}
|
|
403
488
|
|
|
404
489
|
// do some other things to dog...
|
|
405
490
|
// e.g. dogRepository.save(dog)
|
|
406
|
-
return
|
|
407
|
-
}
|
|
491
|
+
return
|
|
492
|
+
}
|
|
408
493
|
|
|
409
494
|
// 2 Pact Message Consumer
|
|
410
495
|
const messagePact = new MessageConsumerPact({
|
|
@@ -412,12 +497,13 @@ const messagePact = new MessageConsumerPact({
|
|
|
412
497
|
dir: path.resolve(process.cwd(), "pacts"),
|
|
413
498
|
pactfileWriteMode: "update",
|
|
414
499
|
provider: "MyJSMessageProvider",
|
|
415
|
-
})
|
|
500
|
+
})
|
|
416
501
|
|
|
417
502
|
describe("receive dog event", () => {
|
|
418
|
-
it("
|
|
503
|
+
it("accepts a valid dog", () => {
|
|
419
504
|
// 3 Consumer expectations
|
|
420
|
-
return (
|
|
505
|
+
return (
|
|
506
|
+
messagePact
|
|
421
507
|
.given("some state")
|
|
422
508
|
.expectsToReceive("a request for a dog")
|
|
423
509
|
.withContent({
|
|
@@ -431,22 +517,22 @@ describe("receive dog event", () => {
|
|
|
431
517
|
|
|
432
518
|
// 4 Verify consumers' ability to handle messages
|
|
433
519
|
.verify(synchronousBodyHandler(dogApiHandler))
|
|
434
|
-
)
|
|
435
|
-
})
|
|
436
|
-
})
|
|
520
|
+
)
|
|
521
|
+
})
|
|
522
|
+
})
|
|
437
523
|
```
|
|
438
524
|
|
|
439
525
|
**Explanation**:
|
|
440
526
|
|
|
441
527
|
1. The Dog API - a contrived API handler example. Expects a dog object and throws an `Error` if it can't handle it.
|
|
442
|
-
|
|
443
|
-
|
|
528
|
+
- In most applications, some form of transactionality exists and communication with a MQ/broker happens.
|
|
529
|
+
- It's important we separate out the protocol bits from the message handling bits, so that we can test that in isolation.
|
|
444
530
|
1. Creates the MessageConsumer class
|
|
445
531
|
1. Setup the expectations for the consumer - here we expect a `dog` object with three fields
|
|
446
532
|
1. Pact will send the message to your message handler. If the handler returns a successful promise, the message is saved, otherwise the test fails. There are a few key things to consider:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
533
|
+
- The actual request body that Pact will send, will be contained within a [Message](https://github.com/pact-foundation/pact-js/tree/master/src/dsl/message.ts) object along with other context, so the body must be retrieved via `content` attribute.
|
|
534
|
+
- All handlers to be tested must be of the shape `(m: Message) => Promise<any>` - that is, they must accept a `Message` and return a `Promise`. This is how we get around all of the various protocols, and will often require a lightweight adapter function to convert it.
|
|
535
|
+
- In this case, we wrap the actual dogApiHandler with a convenience function `synchronousBodyHandler` provided by Pact, which Promisifies the handler and extracts the contents.
|
|
450
536
|
|
|
451
537
|
### Provider (Producer)
|
|
452
538
|
|
|
@@ -455,7 +541,7 @@ A Provider (Producer in messaging parlance) is the system that will be putting a
|
|
|
455
541
|
As per the Consumer case, Pact takes the position of the intermediary (MQ/broker) and checks to see whether or not the Provider sends a message that matches the Consumer's expectations.
|
|
456
542
|
|
|
457
543
|
```js
|
|
458
|
-
const { MessageProvider, Message } = require("@pact-foundation/pact")
|
|
544
|
+
const { MessageProvider, Message } = require("@pact-foundation/pact")
|
|
459
545
|
|
|
460
546
|
// 1 Messaging integration client
|
|
461
547
|
const dogApiClient = {
|
|
@@ -464,37 +550,43 @@ const dogApiClient = {
|
|
|
464
550
|
resolve({
|
|
465
551
|
id: 1,
|
|
466
552
|
name: "fido",
|
|
467
|
-
type: "bulldog"
|
|
468
|
-
})
|
|
469
|
-
})
|
|
470
|
-
}
|
|
471
|
-
}
|
|
553
|
+
type: "bulldog",
|
|
554
|
+
})
|
|
555
|
+
})
|
|
556
|
+
},
|
|
557
|
+
}
|
|
472
558
|
|
|
473
559
|
describe("Message provider tests", () => {
|
|
474
560
|
// 2 Pact setup
|
|
475
561
|
const p = new MessageProviderPact({
|
|
476
562
|
messageProviders: {
|
|
477
|
-
"a request for a dog": () => dogApiClient.createDog()
|
|
563
|
+
"a request for a dog": () => dogApiClient.createDog(),
|
|
478
564
|
},
|
|
479
565
|
provider: "MyJSMessageProvider",
|
|
480
566
|
providerVersion: "1.0.0",
|
|
481
|
-
pactUrls: [
|
|
482
|
-
|
|
567
|
+
pactUrls: [
|
|
568
|
+
path.resolve(
|
|
569
|
+
process.cwd(),
|
|
570
|
+
"pacts",
|
|
571
|
+
"myjsmessageconsumer-myjsmessageprovider.json"
|
|
572
|
+
),
|
|
573
|
+
],
|
|
574
|
+
})
|
|
483
575
|
|
|
484
576
|
// 3 Verify the interactions
|
|
485
577
|
describe("Dog API Client", () => {
|
|
486
|
-
it("
|
|
487
|
-
return p.verify()
|
|
488
|
-
})
|
|
489
|
-
})
|
|
490
|
-
})
|
|
578
|
+
it("sends some dogs", () => {
|
|
579
|
+
return p.verify()
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
})
|
|
491
583
|
```
|
|
492
584
|
|
|
493
585
|
**Explanation**:
|
|
494
586
|
|
|
495
587
|
1. Our API client contains a single function `createDog` which is responsible for generating the message that will be sent to the consumer via some message queue
|
|
496
588
|
1. We configure Pact to stand-in for the queue. The most important bit here is the `messageProviders` block
|
|
497
|
-
|
|
589
|
+
- Similar to the Consumer tests, we map the various interactions that are going to be verified as denoted by their `description` field. In this case, `a request for a dog`, maps to the `createDog` handler. Notice how this matches the original Consumer test.
|
|
498
590
|
1. We can now run the verification process. Pact will read all of the interactions specified by its consumer, and invoke each function that is responsible for generating that message.
|
|
499
591
|
|
|
500
592
|
### Pact Broker Integration
|
|
@@ -515,6 +607,8 @@ _NOTE: Make sure to start the mock service via the `Pact` declaration with the o
|
|
|
515
607
|
|
|
516
608
|
Often times, you find yourself having to re-write regular expressions for common formats. We've created a number of them for you to save you the time:
|
|
517
609
|
|
|
610
|
+
<details><summary>Matchers API</summary>
|
|
611
|
+
|
|
518
612
|
| method | description |
|
|
519
613
|
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
520
614
|
| `boolean` | Match a boolean value (using equality) |
|
|
@@ -530,22 +624,24 @@ Often times, you find yourself having to re-write regular expressions for common
|
|
|
530
624
|
| `ipv6Address` | Will match string containing IP6 formatted address |
|
|
531
625
|
| `uuid` | Will match strings containing UUIDs |
|
|
532
626
|
|
|
627
|
+
</details>
|
|
628
|
+
|
|
533
629
|
### Match based on type
|
|
534
630
|
|
|
535
631
|
```javascript
|
|
536
|
-
const { like } = Matchers
|
|
632
|
+
const { like } = Matchers
|
|
537
633
|
|
|
538
634
|
provider.addInteraction({
|
|
539
635
|
state: "Has some animals",
|
|
540
636
|
uponReceiving: "a request for an animal",
|
|
541
637
|
withRequest: {
|
|
542
638
|
method: "GET",
|
|
543
|
-
path: "/animals/1"
|
|
639
|
+
path: "/animals/1",
|
|
544
640
|
},
|
|
545
641
|
willRespondWith: {
|
|
546
642
|
status: 200,
|
|
547
643
|
headers: {
|
|
548
|
-
"Content-Type": "application/json; charset=utf-8"
|
|
644
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
549
645
|
},
|
|
550
646
|
body: {
|
|
551
647
|
id: 1,
|
|
@@ -553,11 +649,11 @@ provider.addInteraction({
|
|
|
553
649
|
address: like({
|
|
554
650
|
street: "123 Smith St",
|
|
555
651
|
suburb: "Smithsville",
|
|
556
|
-
postcode: 7777
|
|
557
|
-
})
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
})
|
|
652
|
+
postcode: 7777,
|
|
653
|
+
}),
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
})
|
|
561
657
|
```
|
|
562
658
|
|
|
563
659
|
Note that you can wrap a `like` around a single value or an object. When wrapped around an object, all values and child object values will be matched according to types, unless overridden by something more specific like a `term`.
|
|
@@ -569,7 +665,7 @@ Note that you can wrap a `like` around a single value or an object. When wrapped
|
|
|
569
665
|
Matching provides the ability to specify flexible length arrays. For example:
|
|
570
666
|
|
|
571
667
|
```javascript
|
|
572
|
-
pact.eachLike(obj, { min: 3 })
|
|
668
|
+
pact.eachLike(obj, { min: 3 })
|
|
573
669
|
```
|
|
574
670
|
|
|
575
671
|
Where `obj` can be any javascript object, value or Pact.Match. It takes optional argument (`{ min: 3 }`) where min is greater than 0 and defaults to 1 if not provided.
|
|
@@ -577,7 +673,7 @@ Where `obj` can be any javascript object, value or Pact.Match. It takes optional
|
|
|
577
673
|
Below is an example that uses all of the Pact Matchers.
|
|
578
674
|
|
|
579
675
|
```javascript
|
|
580
|
-
const { somethingLike: like, term, eachLike } = pact
|
|
676
|
+
const { somethingLike: like, term, eachLike } = pact
|
|
581
677
|
|
|
582
678
|
const animalBodyExpectation = {
|
|
583
679
|
id: 1,
|
|
@@ -587,41 +683,41 @@ const animalBodyExpectation = {
|
|
|
587
683
|
age: 21,
|
|
588
684
|
gender: term({
|
|
589
685
|
matcher: "F|M",
|
|
590
|
-
generate: "M"
|
|
686
|
+
generate: "M",
|
|
591
687
|
}),
|
|
592
688
|
location: {
|
|
593
689
|
description: "Melbourne Zoo",
|
|
594
690
|
country: "Australia",
|
|
595
|
-
post_code: 3000
|
|
691
|
+
post_code: 3000,
|
|
596
692
|
},
|
|
597
693
|
eligibility: {
|
|
598
694
|
available: true,
|
|
599
|
-
previously_married: false
|
|
695
|
+
previously_married: false,
|
|
600
696
|
},
|
|
601
|
-
children: eachLike({ name: "Sally", age: 2 })
|
|
602
|
-
}
|
|
697
|
+
children: eachLike({ name: "Sally", age: 2 }),
|
|
698
|
+
}
|
|
603
699
|
|
|
604
700
|
// Define animal list payload, reusing existing object matcher
|
|
605
701
|
// Note that using eachLike ensure that all values are matched by type
|
|
606
702
|
const animalListExpectation = eachLike(animalBodyExpectation, {
|
|
607
|
-
min: MIN_ANIMALS
|
|
608
|
-
})
|
|
703
|
+
min: MIN_ANIMALS,
|
|
704
|
+
})
|
|
609
705
|
|
|
610
706
|
provider.addInteraction({
|
|
611
707
|
state: "Has some animals",
|
|
612
708
|
uponReceiving: "a request for all animals",
|
|
613
709
|
withRequest: {
|
|
614
710
|
method: "GET",
|
|
615
|
-
path: "/animals/available"
|
|
711
|
+
path: "/animals/available",
|
|
616
712
|
},
|
|
617
713
|
willRespondWith: {
|
|
618
714
|
status: 200,
|
|
619
715
|
headers: {
|
|
620
|
-
"Content-Type": "application/json; charset=utf-8"
|
|
716
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
621
717
|
},
|
|
622
|
-
body: animalListExpectation
|
|
623
|
-
}
|
|
624
|
-
})
|
|
718
|
+
body: animalListExpectation,
|
|
719
|
+
},
|
|
720
|
+
})
|
|
625
721
|
```
|
|
626
722
|
|
|
627
723
|
### Match by regular expression
|
|
@@ -631,30 +727,30 @@ If none of the above matchers or formats work, you can write your own regex matc
|
|
|
631
727
|
The underlying mock service is written in Ruby, so the regular expression must be in a Ruby format, not a Javascript format.
|
|
632
728
|
|
|
633
729
|
```javascript
|
|
634
|
-
const { term } = pact
|
|
730
|
+
const { term } = pact
|
|
635
731
|
|
|
636
732
|
provider.addInteraction({
|
|
637
733
|
state: "Has some animals",
|
|
638
734
|
uponReceiving: "a request for an animal",
|
|
639
735
|
withRequest: {
|
|
640
736
|
method: "GET",
|
|
641
|
-
path: "/animals/1"
|
|
737
|
+
path: "/animals/1",
|
|
642
738
|
},
|
|
643
739
|
willRespondWith: {
|
|
644
740
|
status: 200,
|
|
645
741
|
headers: {
|
|
646
|
-
"Content-Type": "application/json; charset=utf-8"
|
|
742
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
647
743
|
},
|
|
648
744
|
body: {
|
|
649
745
|
id: 100,
|
|
650
746
|
name: "billy",
|
|
651
747
|
gender: term({
|
|
652
748
|
matcher: "F|M",
|
|
653
|
-
generate: "F"
|
|
654
|
-
})
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
})
|
|
749
|
+
generate: "F",
|
|
750
|
+
}),
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
})
|
|
658
754
|
```
|
|
659
755
|
|
|
660
756
|
## GraphQL API
|
|
@@ -673,19 +769,19 @@ Learn everything in Pact JS in 60 minutes: https://github.com/DiUS/pact-workshop
|
|
|
673
769
|
|
|
674
770
|
### HTTP APIs
|
|
675
771
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
772
|
+
- [Complete Example (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/e2e)
|
|
773
|
+
- [Pact with AVA (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/ava)
|
|
774
|
+
- [Pact with Jest (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/jest)
|
|
775
|
+
- [Pact with TypeScript + Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/typescript)
|
|
776
|
+
- [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha)
|
|
777
|
+
- [Pact with GraphQL](https://github.com/pact-foundation/pact-js/tree/master/examples/graphql)
|
|
778
|
+
- [Pact with Karma + Jasmine](https://github.com/pact-foundation/pact-js/tree/master/karma/jasmine)
|
|
779
|
+
- [Pact with Karma + Mocha](https://github.com/pact-foundation/pact-js/tree/master/karma/mocha)
|
|
684
780
|
|
|
685
781
|
### Asynchronous APIs
|
|
686
782
|
|
|
687
|
-
|
|
688
|
-
|
|
783
|
+
- [Asynchronous messages](https://github.com/pact-foundation/pact-js/tree/master/examples/messages)
|
|
784
|
+
- [Serverless](https://github.com/pact-foundation/pact-js/tree/master/examples/serverless)
|
|
689
785
|
|
|
690
786
|
## Using Pact in non-Node environments
|
|
691
787
|
|
|
@@ -757,11 +853,11 @@ require.config({
|
|
|
757
853
|
baseUrl: "/base",
|
|
758
854
|
paths: {
|
|
759
855
|
Pact: "node_modules/pact-web/pact-web",
|
|
760
|
-
client: "js/client"
|
|
856
|
+
client: "js/client",
|
|
761
857
|
},
|
|
762
858
|
deps: allTestFiles,
|
|
763
|
-
callback: window.__karma__.start
|
|
764
|
-
})
|
|
859
|
+
callback: window.__karma__.start,
|
|
860
|
+
})
|
|
765
861
|
```
|
|
766
862
|
|
|
767
863
|
See this [Stack Overflow](https://stackoverflow.com/a/44170373/1008568) question for background, and
|
|
@@ -788,8 +884,8 @@ When all of your tests have completed, the result is the union of the all of the
|
|
|
788
884
|
|
|
789
885
|
See the following examples for working parallel tests:
|
|
790
886
|
|
|
791
|
-
|
|
792
|
-
|
|
887
|
+
- [Pact with AVA (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/ava)
|
|
888
|
+
- [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha)
|
|
793
889
|
|
|
794
890
|
### Splitting tests across multiple files
|
|
795
891
|
|
|
@@ -909,9 +1005,9 @@ Join us in [Slack](slack.pact.io)
|
|
|
909
1005
|
|
|
910
1006
|
or chat to us at
|
|
911
1007
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1008
|
+
- Twitter: [@pact_up](https://twitter.com/pact_up)
|
|
1009
|
+
- Stack Overflow: https://stackoverflow.com/questions/tagged/pact
|
|
1010
|
+
- Google users group: https://groups.google.com/forum/#!forum/pact-support
|
|
915
1011
|
|
|
916
1012
|
[getting started with pact]: http://dius.com.au/2016/02/03/microservices-pact/
|
|
917
1013
|
[v4]: https://github.com/pact-foundation/pact-js/tree/4.x.x
|