@pact-foundation/pact 10.0.0-beta.9 → 10.0.0
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 +2451 -1
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +49 -12
- package/MIGRATION.md +77 -0
- package/README.md +180 -1274
- package/RELEASING.md +63 -59
- package/package.json +96 -98
- package/src/common/jsonTypes.d.ts +5 -0
- package/src/common/jsonTypes.js +3 -0
- package/src/common/jsonTypes.js.map +1 -0
- package/src/common/logger.d.ts +10 -0
- package/src/common/logger.js +32 -0
- package/src/common/logger.js.map +1 -0
- package/src/common/net.d.ts +9 -0
- package/src/common/net.js +82 -0
- package/src/common/net.js.map +1 -0
- package/{common → src/common}/net.spec.d.ts +0 -0
- package/src/common/net.spec.js +69 -0
- package/src/common/net.spec.js.map +1 -0
- package/src/common/request.d.ts +21 -0
- package/src/common/request.js +110 -0
- package/src/common/request.js.map +1 -0
- package/{common → src/common}/request.spec.d.ts +0 -0
- package/src/common/request.spec.js +69 -0
- package/src/common/request.spec.js.map +1 -0
- package/src/common/spec.d.ts +2 -0
- package/src/common/spec.js +22 -0
- package/src/common/spec.js.map +1 -0
- package/{dsl → src/dsl}/apolloGraphql.d.ts +1 -1
- package/{dsl → src/dsl}/apolloGraphql.js +4 -1
- package/src/dsl/apolloGraphql.js.map +1 -0
- package/{dsl → src/dsl}/apolloGraphql.spec.d.ts +0 -0
- package/src/dsl/apolloGraphql.spec.js +74 -0
- package/src/dsl/apolloGraphql.spec.js.map +1 -0
- package/{dsl → src/dsl}/graphql.d.ts +19 -8
- package/{dsl → src/dsl}/graphql.js +59 -31
- package/src/dsl/graphql.js.map +1 -0
- package/{dsl → src/dsl}/graphql.spec.d.ts +0 -0
- package/src/dsl/graphql.spec.js +229 -0
- package/src/dsl/graphql.spec.js.map +1 -0
- package/{dsl → src/dsl}/interaction.d.ts +22 -20
- package/src/dsl/interaction.js +144 -0
- package/src/dsl/interaction.js.map +1 -0
- package/{dsl → src/dsl}/interaction.spec.d.ts +0 -0
- package/src/dsl/interaction.spec.js +214 -0
- package/src/dsl/interaction.spec.js.map +1 -0
- package/src/dsl/matchers.d.ts +143 -0
- package/{dsl → src/dsl}/matchers.js +92 -93
- package/src/dsl/matchers.js.map +1 -0
- package/{dsl → src/dsl}/matchers.spec.d.ts +0 -0
- package/src/dsl/matchers.spec.js +668 -0
- package/src/dsl/matchers.spec.js.map +1 -0
- package/{dsl → src/dsl}/message.d.ts +32 -12
- package/{dsl → src/dsl}/message.js +0 -0
- package/{dsl → src/dsl}/message.js.map +1 -1
- package/src/dsl/mockService.d.ts +13 -0
- package/src/dsl/mockService.js +3 -0
- package/src/dsl/mockService.js.map +1 -0
- package/{dsl → src/dsl}/options.d.ts +7 -6
- package/{dsl → src/dsl}/options.js +0 -0
- package/{dsl → src/dsl}/options.js.map +1 -1
- package/src/dsl/verifier/index.d.ts +1 -0
- package/src/dsl/verifier/index.js +18 -0
- package/src/dsl/verifier/index.js.map +1 -0
- package/src/dsl/verifier/proxy/hooks.d.ts +4 -0
- package/src/dsl/verifier/proxy/hooks.js +109 -0
- package/src/dsl/verifier/proxy/hooks.js.map +1 -0
- package/src/dsl/verifier/proxy/index.d.ts +1 -0
- package/src/dsl/verifier/proxy/index.js +18 -0
- package/src/dsl/verifier/proxy/index.js.map +1 -0
- package/src/dsl/verifier/proxy/parseBody.d.ts +7 -0
- package/src/dsl/verifier/proxy/parseBody.js +22 -0
- package/src/dsl/verifier/proxy/parseBody.js.map +1 -0
- package/{dsl/mockService.spec.d.ts → src/dsl/verifier/proxy/parseBody.spec.d.ts} +0 -0
- package/src/dsl/verifier/proxy/parseBody.spec.js +118 -0
- package/src/dsl/verifier/proxy/parseBody.spec.js.map +1 -0
- package/src/dsl/verifier/proxy/proxy.d.ts +4 -0
- package/src/dsl/verifier/proxy/proxy.js +92 -0
- package/src/dsl/verifier/proxy/proxy.js.map +1 -0
- package/{dsl/publisher.spec.d.ts → src/dsl/verifier/proxy/proxy.spec.d.ts} +0 -0
- package/src/dsl/verifier/proxy/proxy.spec.js +33 -0
- package/src/dsl/verifier/proxy/proxy.spec.js.map +1 -0
- package/src/dsl/verifier/proxy/stateHandler/index.d.ts +1 -0
- package/src/dsl/verifier/proxy/stateHandler/index.js +18 -0
- package/src/dsl/verifier/proxy/stateHandler/index.js.map +1 -0
- package/src/dsl/verifier/proxy/stateHandler/setupStates.d.ts +3 -0
- package/src/dsl/verifier/proxy/stateHandler/setupStates.js +48 -0
- package/src/dsl/verifier/proxy/stateHandler/setupStates.js.map +1 -0
- package/{dsl/verifier.spec.d.ts → src/dsl/verifier/proxy/stateHandler/setupStates.spec.d.ts} +0 -0
- package/src/dsl/verifier/proxy/stateHandler/setupStates.spec.js +191 -0
- package/src/dsl/verifier/proxy/stateHandler/setupStates.spec.js.map +1 -0
- package/src/dsl/verifier/proxy/stateHandler/stateHandler.d.ts +3 -0
- package/src/dsl/verifier/proxy/stateHandler/stateHandler.js +14 -0
- package/src/dsl/verifier/proxy/stateHandler/stateHandler.js.map +1 -0
- package/{httpPact.spec.d.ts → src/dsl/verifier/proxy/stateHandler/stateHandler.spec.d.ts} +0 -0
- package/src/dsl/verifier/proxy/stateHandler/stateHandler.spec.js +130 -0
- package/src/dsl/verifier/proxy/stateHandler/stateHandler.spec.js.map +1 -0
- package/src/dsl/verifier/proxy/tracer.d.ts +3 -0
- package/src/dsl/verifier/proxy/tracer.js +52 -0
- package/src/dsl/verifier/proxy/tracer.js.map +1 -0
- package/src/dsl/verifier/proxy/types.d.ts +48 -0
- package/src/dsl/verifier/proxy/types.js +3 -0
- package/src/dsl/verifier/proxy/types.js.map +1 -0
- package/src/dsl/verifier/types.d.ts +3 -0
- package/src/dsl/verifier/types.js +3 -0
- package/src/dsl/verifier/types.js.map +1 -0
- package/src/dsl/verifier/verifier.d.ts +16 -0
- package/src/dsl/verifier/verifier.js +129 -0
- package/src/dsl/verifier/verifier.js.map +1 -0
- package/{messageConsumerPact.spec.d.ts → src/dsl/verifier/verifier.spec.d.ts} +0 -0
- package/src/dsl/verifier/verifier.spec.js +172 -0
- package/src/dsl/verifier/verifier.spec.js.map +1 -0
- package/{errors → src/errors}/configurationError.d.ts +0 -0
- package/{errors → src/errors}/configurationError.js +3 -1
- package/src/errors/configurationError.js.map +1 -0
- package/{errors → src/errors}/graphQLQueryError.d.ts +0 -0
- package/{errors → src/errors}/graphQLQueryError.js +3 -1
- package/src/errors/graphQLQueryError.js.map +1 -0
- package/{errors → src/errors}/matcherError.d.ts +0 -0
- package/{errors → src/errors}/matcherError.js +3 -1
- package/src/errors/matcherError.js.map +1 -0
- package/{errors → src/errors}/verificationError.d.ts +0 -0
- package/{errors → src/errors}/verificationError.js +3 -1
- package/src/errors/verificationError.js.map +1 -0
- package/src/httpPact/ffi.d.ts +15 -0
- package/src/httpPact/ffi.js +91 -0
- package/src/httpPact/ffi.js.map +1 -0
- package/{messageProviderPact.spec.d.ts → src/httpPact/ffi.spec.d.ts} +0 -0
- package/src/httpPact/ffi.spec.js +81 -0
- package/src/httpPact/ffi.spec.js.map +1 -0
- package/{httpPact.d.ts → src/httpPact/index.d.ts} +12 -12
- package/src/httpPact/index.js +311 -0
- package/src/httpPact/index.js.map +1 -0
- package/{pact-web.spec.d.ts → src/httpPact/index.spec.d.ts} +0 -0
- package/src/httpPact/index.spec.js +238 -0
- package/src/httpPact/index.spec.js.map +1 -0
- package/src/httpPact/tracing.d.ts +1 -0
- package/src/httpPact/tracing.js +53 -0
- package/src/httpPact/tracing.js.map +1 -0
- package/{pact.d.ts → src/index.d.ts} +22 -21
- package/src/index.js +107 -0
- package/src/index.js.map +1 -0
- package/src/messageConsumerPact.d.ts +86 -0
- package/src/messageConsumerPact.js +222 -0
- package/src/messageConsumerPact.js.map +1 -0
- package/src/messageConsumerPact.spec.d.ts +1 -0
- package/src/messageConsumerPact.spec.js +117 -0
- package/src/messageConsumerPact.spec.js.map +1 -0
- package/{messageProviderPact.d.ts → src/messageProviderPact.d.ts} +7 -4
- package/src/messageProviderPact.js +190 -0
- package/src/messageProviderPact.js.map +1 -0
- package/src/messageProviderPact.spec.d.ts +1 -0
- package/src/messageProviderPact.spec.js +183 -0
- package/src/messageProviderPact.spec.js.map +1 -0
- package/{pact.integration.spec.d.ts → src/pact.integration.spec.d.ts} +0 -0
- package/{pact.integration.spec.js → src/pact.integration.spec.js} +0 -0
- package/src/pact.integration.spec.js.map +1 -0
- package/src/v3/display.d.ts +6 -0
- package/src/v3/display.js +83 -0
- package/src/v3/display.js.map +1 -0
- package/src/v3/index.d.ts +17 -0
- package/src/v3/index.js +47 -0
- package/src/v3/index.js.map +1 -0
- package/src/v3/matchers.d.ts +182 -0
- package/src/v3/matchers.js +443 -0
- package/src/v3/matchers.js.map +1 -0
- package/src/v3/matchers.spec.d.ts +1 -0
- package/src/v3/matchers.spec.js +461 -0
- package/src/v3/matchers.spec.js.map +1 -0
- package/src/v3/pact.d.ts +21 -0
- package/src/v3/pact.js +240 -0
- package/src/v3/pact.js.map +1 -0
- package/src/v3/types.d.ts +81 -0
- package/src/v3/types.js +11 -0
- package/src/v3/types.js.map +1 -0
- package/{v3 → src/v3}/xml/xmlBuilder.d.ts +2 -2
- package/{v3 → src/v3}/xml/xmlBuilder.js +2 -1
- package/src/v3/xml/xmlBuilder.js.map +1 -0
- package/src/v3/xml/xmlElement.d.ts +27 -0
- package/{v3 → src/v3}/xml/xmlElement.js +21 -15
- package/src/v3/xml/xmlElement.js.map +1 -0
- package/src/v3/xml/xmlElement.spec.d.ts +1 -0
- package/src/v3/xml/xmlElement.spec.js +102 -0
- package/src/v3/xml/xmlElement.spec.js.map +1 -0
- package/{v3 → src/v3}/xml/xmlNode.d.ts +0 -0
- package/{v3 → src/v3}/xml/xmlNode.js +1 -0
- package/src/v3/xml/xmlNode.js.map +1 -0
- package/src/v3/xml/xmlText.d.ts +7 -0
- package/{v3 → src/v3}/xml/xmlText.js +4 -1
- package/src/v3/xml/xmlText.js.map +1 -0
- package/test/helper.d.ts +1 -0
- package/test/helper.js +37 -0
- package/test/helper.js.map +1 -0
- package/ROADMAP.md +0 -20
- package/common/logger.d.ts +0 -7
- package/common/logger.js +0 -56
- package/common/logger.js.map +0 -1
- package/common/metadata.d.ts +0 -0
- package/common/metadata.js +0 -2
- package/common/metadata.js.map +0 -1
- package/common/net.d.ts +0 -9
- package/common/net.js +0 -48
- package/common/net.js.map +0 -1
- package/common/net.spec.js +0 -67
- package/common/net.spec.js.map +0 -1
- package/common/request.d.ts +0 -14
- package/common/request.js +0 -47
- package/common/request.js.map +0 -1
- package/common/request.spec.js +0 -71
- package/common/request.spec.js.map +0 -1
- package/common/utils.d.ts +0 -2
- package/common/utils.js +0 -10
- package/common/utils.js.map +0 -1
- package/dsl/apolloGraphql.js.map +0 -1
- package/dsl/apolloGraphql.spec.js +0 -47
- package/dsl/apolloGraphql.spec.js.map +0 -1
- package/dsl/graphql.js.map +0 -1
- package/dsl/graphql.spec.js +0 -150
- package/dsl/graphql.spec.js.map +0 -1
- package/dsl/interaction.js +0 -112
- package/dsl/interaction.js.map +0 -1
- package/dsl/interaction.spec.js +0 -217
- package/dsl/interaction.spec.js.map +0 -1
- package/dsl/matchers.d.ts +0 -269
- package/dsl/matchers.js.map +0 -1
- package/dsl/matchers.spec.js +0 -563
- package/dsl/matchers.spec.js.map +0 -1
- package/dsl/mockService.d.ts +0 -58
- package/dsl/mockService.js +0 -74
- package/dsl/mockService.js.map +0 -1
- package/dsl/mockService.spec.js +0 -126
- package/dsl/mockService.spec.js.map +0 -1
- package/dsl/publisher.d.ts +0 -6
- package/dsl/publisher.js +0 -19
- package/dsl/publisher.js.map +0 -1
- package/dsl/publisher.spec.js +0 -21
- package/dsl/publisher.spec.js.map +0 -1
- package/dsl/verifier.d.ts +0 -39
- package/dsl/verifier.js +0 -173
- package/dsl/verifier.js.map +0 -1
- package/dsl/verifier.spec.js +0 -299
- package/dsl/verifier.spec.js.map +0 -1
- package/errors/configurationError.js.map +0 -1
- package/errors/graphQLQueryError.js.map +0 -1
- package/errors/matcherError.js.map +0 -1
- package/errors/verificationError.js.map +0 -1
- package/httpPact.js +0 -206
- package/httpPact.js.map +0 -1
- package/httpPact.spec.js +0 -337
- package/httpPact.spec.js.map +0 -1
- package/messageConsumerPact.d.ts +0 -67
- package/messageConsumerPact.js +0 -161
- package/messageConsumerPact.js.map +0 -1
- package/messageConsumerPact.spec.js +0 -161
- package/messageConsumerPact.spec.js.map +0 -1
- package/messageProviderPact.js +0 -139
- package/messageProviderPact.js.map +0 -1
- package/messageProviderPact.spec.js +0 -143
- package/messageProviderPact.spec.js.map +0 -1
- package/pact-web.d.ts +0 -100
- package/pact-web.js +0 -151
- package/pact-web.js.map +0 -1
- package/pact-web.spec.js +0 -191
- package/pact-web.spec.js.map +0 -1
- package/pact.integration.spec.js.map +0 -1
- package/pact.js +0 -74
- package/pact.js.map +0 -1
- package/v3/index.d.ts +0 -23
- package/v3/index.js +0 -33
- package/v3/index.js.map +0 -1
- package/v3/matchers.d.ts +0 -199
- package/v3/matchers.js +0 -305
- package/v3/matchers.js.map +0 -1
- package/v3/pact.d.ts +0 -47
- package/v3/pact.js +0 -101
- package/v3/pact.js.map +0 -1
- package/v3/verifier.d.ts +0 -27
- package/v3/verifier.js +0 -46
- package/v3/verifier.js.map +0 -1
- package/v3/xml/xmlBuilder.js.map +0 -1
- package/v3/xml/xmlElement.d.ts +0 -34
- package/v3/xml/xmlElement.js.map +0 -1
- package/v3/xml/xmlNode.js.map +0 -1
- package/v3/xml/xmlText.d.ts +0 -6
- package/v3/xml/xmlText.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,1356 +1,262 @@
|
|
|
1
|
+
****<span align="center">
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
1
5
|
# Pact JS
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
](https://github.com/pact-foundation/pact-js/actions?query=workflow%3A%22Build%2C+test%2C+test+all+examples%22)
|
|
6
10
|
[](https://www.npmjs.com/package/@pact-foundation/pact)
|
|
11
|
+

|
|
7
12
|
[](https://coveralls.io/github/pact-foundation/pact-js?branch=master)
|
|
8
13
|
[](https://codeclimate.com/github/pact-foundation/pact-js)
|
|
9
14
|
[](https://codeclimate.com/github/pact-foundation/pact-js)
|
|
10
15
|
[](https://snyk.io/test/github/pact-foundation/pact-js?targetFile=package.json)
|
|
11
16
|
[](https://github.com/pact-foundation/pact-js/blob/master/LICENSE)
|
|
12
|
-
[ for Javascript.
|
|
15
|
-
|
|
16
|
-
From the [Pact website](http://docs.pact.io/):
|
|
17
|
+
[](https://slack.pact.io)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
#### Fast, easy and reliable testing for your APIs and microservices.
|
|
19
20
|
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
> Consumer Driven Contracts is a pattern that drives the development of the Provider from its Consumers point of view.
|
|
23
|
-
|
|
24
|
-
> Pact is a testing tool that guarantees those Contracts are satisfied.
|
|
25
|
-
|
|
26
|
-
Read [Getting started with Pact] for more information for beginners.
|
|
21
|
+
</span>
|
|
27
22
|
|
|
23
|
+
<br />
|
|
28
24
|
<p align="center">
|
|
29
|
-
<a href="https://
|
|
30
|
-
<img width="880" src="https://raw.githubusercontent.com/pact-foundation/pact-js/master/.github/pact.svg?sanitize=true&t=1">
|
|
31
|
-
</a>
|
|
25
|
+
<a href="https://docs.pact.io"><img src="https://user-images.githubusercontent.com/53900/180370118-f11c61f3-4ae0-496f-98fa-052fdfad409e.gif" alt="Pact JS Demo"/></a>
|
|
32
26
|
</p>
|
|
27
|
+
<br />
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- [Installation](#installation)
|
|
38
|
-
- [Do Not Track](#do-not-track)
|
|
39
|
-
- [Which Library/Package should I use?](#which-librarypackage-should-i-use)
|
|
40
|
-
- [Using Pact JS](#using-pact-js)
|
|
41
|
-
- [HTTP API Testing](#http-api-testing)
|
|
42
|
-
- [Consumer Side Testing](#consumer-side-testing)
|
|
43
|
-
- [API](#api)
|
|
44
|
-
- [Example](#example)
|
|
45
|
-
- [Provider API Testing](#provider-api-testing)
|
|
46
|
-
- [Verification Options](#verification-options)
|
|
47
|
-
- [API with Provider States](#api-with-provider-states)
|
|
48
|
-
- [Pending Pacts](#pending-pacts)
|
|
49
|
-
- [WIP Pacts](#wip-pacts)
|
|
50
|
-
- [Verifying multiple contracts with the same tag (e.g. for Mobile use cases)](#verifying-multiple-contracts-with-the-same-tag-eg-for-mobile-use-cases)
|
|
51
|
-
- [Modify Requests Prior to Verification (Request Filters)](#modify-requests-prior-to-verification-request-filters)
|
|
52
|
-
- [Publishing Pacts to a Broker](#publishing-pacts-to-a-broker)
|
|
53
|
-
- [Pact publishing options](#pact-publishing-options)
|
|
54
|
-
- [Publishing Verification Results to a Pact Broker](#publishing-verification-results-to-a-pact-broker)
|
|
55
|
-
- [Asynchronous API Testing](#asynchronous-api-testing)
|
|
56
|
-
- [Consumer](#consumer)
|
|
57
|
-
- [Provider (Producer)](#provider-producer)
|
|
58
|
-
- [Pact Broker Integration](#pact-broker-integration)
|
|
59
|
-
- [Matching](#matching)
|
|
60
|
-
- [Match common formats](#match-common-formats)
|
|
61
|
-
- [Match based on type](#match-based-on-type)
|
|
62
|
-
- [Match based on arrays](#match-based-on-arrays)
|
|
63
|
-
- [Match by regular expression](#match-by-regular-expression)
|
|
64
|
-
- [GraphQL API](#graphql-api)
|
|
65
|
-
- [Tutorial (60 minutes)](#tutorial-60-minutes)
|
|
66
|
-
- [Examples](#examples)
|
|
67
|
-
- [HTTP APIs](#http-apis)
|
|
68
|
-
- [Asynchronous APIs](#asynchronous-apis)
|
|
69
|
-
- [Using Pact in non-Node environments](#using-pact-in-non-node-environments)
|
|
70
|
-
- [Using Pact with Karma](#using-pact-with-karma)
|
|
71
|
-
- [Using Pact with RequireJS](#using-pact-with-requirejs)
|
|
72
|
-
- [Pact JS V3](#pact-js-v3)
|
|
73
|
-
- [Using the V3 matching rules](#using-the-v3-matching-rules)
|
|
74
|
-
- [Using Pact with XML](#using-pact-with-xml)
|
|
75
|
-
- [Verifying providers with VerifierV3](#verifying-providers-with-verifierv3)
|
|
76
|
-
- [Request Filters](#request-filters)
|
|
77
|
-
- [Provider state callbacks](#provider-state-callbacks)
|
|
78
|
-
- [Troubleshooting / FAQs](#troubleshooting--faqs)
|
|
79
|
-
- [Alpine + Docker](#alpine--docker)
|
|
80
|
-
- [Parallel tests](#parallel-tests)
|
|
81
|
-
- [Splitting tests across multiple files](#splitting-tests-across-multiple-files)
|
|
82
|
-
- [Test fails when it should pass](#test-fails-when-it-should-pass)
|
|
83
|
-
- [Test intermittent failures](#test-intermittent-failures)
|
|
84
|
-
- [Re-run specific verification failures](#re-run-specific-verification-failures)
|
|
85
|
-
- [Timeout](#timeout)
|
|
86
|
-
- [Usage with Jest](#usage-with-jest)
|
|
87
|
-
- [Usage with Angular](#usage-with-angular)
|
|
88
|
-
- [Debugging](#debugging)
|
|
89
|
-
- [Contributing](#contributing)
|
|
90
|
-
- [Contact](#contact)
|
|
91
|
-
|
|
92
|
-
<!-- /TOC -->
|
|
93
|
-
|
|
94
|
-
## Installation
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
npm i -S @pact-foundation/pact@latest
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Do Not Track
|
|
101
|
-
|
|
102
|
-
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`:
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
{
|
|
106
|
-
"name": "some-project",
|
|
107
|
-
...
|
|
108
|
-
"config": {
|
|
109
|
-
"pact_do_not_track": true
|
|
110
|
-
},
|
|
111
|
-
...
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
See the [Changelog] for versions and their history.
|
|
116
|
-
|
|
117
|
-
## Which Library/Package should I use?
|
|
118
|
-
|
|
119
|
-
TL;DR - you almost always want Pact JS.
|
|
120
|
-
|
|
121
|
-
| Purpose | Library | Comments |
|
|
122
|
-
| ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
-
| Synchronous / HTTP APIs | Pact JS | |
|
|
124
|
-
| Asynchronous APIs | Pact JS | |
|
|
125
|
-
| Node.js | Pact JS | |
|
|
126
|
-
| Browser testing | Pact Web | You probably still want Pact JS. See [Using Pact in non-Node environments](#using-pact-in-non-node-environments) \* |
|
|
127
|
-
| Isomorphic testing | Pact Web | You probably still want Pact JS. See [Using Pact in non-Node environments](#using-pact-in-non-node-environments) \* |
|
|
128
|
-
| Publishing to Pact Broker | Pact JS | |
|
|
129
|
-
|
|
130
|
-
\* The "I need to run it in the browser" question comes up occasionally. The question is this - for your JS code to be able to make a call to another API, is this dependent on browser-specific code? In most cases, people use tools like React/Angular which have libraries that work on the server and client side, in which case, these tests don't need to run in a browser and could instead be executed in a Node.js environment.
|
|
131
|
-
|
|
132
|
-
## Using Pact JS
|
|
133
|
-
|
|
134
|
-
Pact supports [synchronous request-response style HTTP interactions](#http-api-testing) and [asynchronous interactions](#asynchronous-api-testing) with JSON-formatted payloads.
|
|
135
|
-
|
|
136
|
-
## HTTP API Testing
|
|
137
|
-
|
|
138
|
-
### Consumer Side Testing
|
|
139
|
-
|
|
140
|
-
To use the library on your tests, add the pact dependency:
|
|
141
|
-
|
|
142
|
-
```javascript
|
|
143
|
-
const { Pact } = require("@pact-foundation/pact")
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
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:
|
|
147
|
-
|
|
148
|
-
#### API
|
|
149
|
-
|
|
150
|
-
<details><summary>Consumer API</summary>
|
|
151
|
-
|
|
152
|
-
| API | Options | Returns | Description |
|
|
153
|
-
| ------------------- | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
154
|
-
| `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. |
|
|
155
|
-
| `setup()` | n/a | `Promise` | Start the Mock Server and wait for it to be available. You would normally call this only once in a `beforeAll(...)` type clause |
|
|
156
|
-
| `addInteraction()` | `Object` | `Promise` | Register an expectation on the Mock Server, which must be called by your test case(s). You can add multiple interactions per server, and each test would normally contain one or more of these. These will be validated and written to a pact if successful. |
|
|
157
|
-
| `verify()` | n/a | `Promise` | Verifies that all interactions specified. This should be called once per test, to ensure your expectations were correct |
|
|
158
|
-
| `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. |
|
|
159
|
-
|
|
160
|
-
</details>
|
|
161
|
-
|
|
162
|
-
<details><summary>Constructor</summary>
|
|
163
|
-
|
|
164
|
-
| Parameter | Required? | Type | Description |
|
|
165
|
-
| ------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------- |
|
|
166
|
-
| `consumer` | yes | string | The name of the consumer |
|
|
167
|
-
| `provider` | yes | string | The name of the provider |
|
|
168
|
-
| `port` | no | number | The port to run the mock service on, defaults to 1234 |
|
|
169
|
-
| `host` | no | string | The host to run the mock service, defaults to 127.0.0.1 |
|
|
170
|
-
| `ssl` | no | boolean | SSL flag to identify the protocol to be used (default false, HTTP) |
|
|
171
|
-
| `sslcert` | no | string | Path to SSL certificate to serve on the mock service |
|
|
172
|
-
| `sslkey` | no | string | Path to SSL key to serve on the mock service |
|
|
173
|
-
| `dir` | no | string | Directory to output pact files |
|
|
174
|
-
| `log` | no | string | File to log to |
|
|
175
|
-
| `logLevel` | no | string | Log level: one of 'trace', 'debug', 'info', 'error', 'fatal' or 'warn' |
|
|
176
|
-
| `spec` | no | number | Pact specification version (defaults to 2) |
|
|
177
|
-
| `cors` | no | boolean | Allow CORS OPTION requests to be accepted, defaults to false |
|
|
178
|
-
| `pactfileWriteMode` | no | string | Control how the Pact files are written. Choices: 'overwrite' 'update' or 'none'. Defaults to 'overwrite' |
|
|
179
|
-
|
|
180
|
-
</details>
|
|
181
|
-
|
|
182
|
-
#### Example
|
|
183
|
-
|
|
184
|
-
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:
|
|
185
|
-
|
|
186
|
-
1. Create the Pact object
|
|
187
|
-
1. Start the Mock Provider that will stand in for your actual Provider
|
|
188
|
-
1. Add the interactions you expect your consumer code to make when executing the tests
|
|
189
|
-
1. Write your tests - the important thing here is that you test the outbound _collaborating_ function which calls the Provider, and not just issue raw http requests to the Provider. This ensures you are testing your actual running code, just like you would in any other unit test, and that the tests will always remain up to date with what your consumer is doing.
|
|
190
|
-
1. Validate the expected interactions were made between your consumer and the Mock Service
|
|
191
|
-
1. Generate the pact(s)
|
|
192
|
-
|
|
193
|
-
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).
|
|
194
|
-
|
|
195
|
-
```javascript
|
|
196
|
-
const path = require("path")
|
|
197
|
-
const chai = require("chai")
|
|
198
|
-
const { Pact } = require("@pact-foundation/pact")
|
|
199
|
-
const chaiAsPromised = require("chai-as-promised")
|
|
200
|
-
const expect = chai.expect
|
|
201
|
-
|
|
202
|
-
chai.use(chaiAsPromised)
|
|
203
|
-
|
|
204
|
-
describe("Pact", () => {
|
|
205
|
-
// (1) Create the Pact object to represent your provider
|
|
206
|
-
const provider = new Pact({
|
|
207
|
-
consumer: "TodoApp",
|
|
208
|
-
provider: "TodoService",
|
|
209
|
-
port: 1234,
|
|
210
|
-
log: path.resolve(process.cwd(), "logs", "pact.log"),
|
|
211
|
-
dir: path.resolve(process.cwd(), "pacts"),
|
|
212
|
-
logLevel: "INFO",
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
// this is the response you expect from your Provider
|
|
216
|
-
const EXPECTED_BODY = [
|
|
217
|
-
{
|
|
218
|
-
id: 1,
|
|
219
|
-
name: "Project 1",
|
|
220
|
-
due: "2016-02-11T09:46:56.023Z",
|
|
221
|
-
tasks: [
|
|
222
|
-
{ id: 1, name: "Do the laundry", done: true },
|
|
223
|
-
{ id: 2, name: "Do the dishes", done: false },
|
|
224
|
-
{ id: 3, name: "Do the backyard", done: false },
|
|
225
|
-
{ id: 4, name: "Do nothing", done: false },
|
|
226
|
-
],
|
|
227
|
-
},
|
|
228
|
-
]
|
|
229
|
-
|
|
230
|
-
const todoApp = new TodoApp()
|
|
231
|
-
|
|
232
|
-
context("when there are a list of projects", () => {
|
|
233
|
-
describe("and there is a valid user session", () => {
|
|
234
|
-
before(() =>
|
|
235
|
-
provider
|
|
236
|
-
// (2) Start the mock server
|
|
237
|
-
.setup()
|
|
238
|
-
// (3) add interactions to the Mock Server, as many as required
|
|
239
|
-
.then(() =>
|
|
240
|
-
provider.addInteraction({
|
|
241
|
-
// The 'state' field specifies a "Provider State"
|
|
242
|
-
state: "i have a list of projects",
|
|
243
|
-
uponReceiving: "a request for projects",
|
|
244
|
-
withRequest: {
|
|
245
|
-
method: "GET",
|
|
246
|
-
path: "/projects",
|
|
247
|
-
headers: { Accept: "application/json" },
|
|
248
|
-
},
|
|
249
|
-
willRespondWith: {
|
|
250
|
-
status: 200,
|
|
251
|
-
headers: { "Content-Type": "application/json" },
|
|
252
|
-
body: EXPECTED_BODY,
|
|
253
|
-
},
|
|
254
|
-
})
|
|
255
|
-
)
|
|
256
|
-
)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// (4) write your test(s)
|
|
260
|
-
it("generates a list of TODOs for the main screen", async () => {
|
|
261
|
-
const projects = await todoApp.getProjects() // <- this method would make the remote http call
|
|
262
|
-
expect(projects).to.be.a("array")
|
|
263
|
-
expect(projects).to.have.deep.property("projects[0].id", 1)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
// (5) validate the interactions you've registered and expected occurred
|
|
267
|
-
// this will throw an error if it fails telling you what went wrong
|
|
268
|
-
// This should be performed once per interaction test
|
|
269
|
-
afterEach(() => provider.verify())
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// (6) write the pact file for this consumer-provider pair,
|
|
273
|
-
// and shutdown the associated mock server.
|
|
274
|
-
// You should do this only _once_ per Provider you are testing,
|
|
275
|
-
// and after _all_ tests have run for that suite
|
|
276
|
-
after(() => provider.finalize())
|
|
277
|
-
})
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Provider API Testing
|
|
281
|
-
|
|
282
|
-
<details><summary>Provider API</summary>
|
|
29
|
+
<table>
|
|
30
|
+
<tr>
|
|
31
|
+
<td>
|
|
283
32
|
|
|
284
|
-
|
|
33
|
+
**Pact** is the de-facto API contract testing tool. Replace expensive and brittle end-to-end integration tests with fast, reliable and easy to debug unit tests.
|
|
285
34
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
35
|
+
- ⚡ Lightning fast
|
|
36
|
+
- 🎈 Effortless full-stack integration testing - from the front-end to the back-end
|
|
37
|
+
- 🔌 Supports HTTP/REST and event-driven systems
|
|
38
|
+
- 🛠️ Configurable mock server
|
|
39
|
+
- 😌 Powerful matching rules prevents brittle tests
|
|
40
|
+
- 🤝 Integrates with Pact Broker / Pactflow for powerful CI/CD workflows
|
|
41
|
+
- 🔡 Supports 12+ languages
|
|
289
42
|
|
|
290
|
-
|
|
43
|
+
**Why use Pact?**
|
|
291
44
|
|
|
292
|
-
|
|
293
|
-
1. Optionally, instrument your API with ability to configure [provider states](https://github.com/pact-foundation/pact-provider-verifier/)
|
|
294
|
-
1. Then run the Provider side verification step
|
|
45
|
+
Contract testing with Pact lets you:
|
|
295
46
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
```
|
|
47
|
+
- ⚡ Test locally
|
|
48
|
+
- 🚀 Deploy faster
|
|
49
|
+
- ⬇️ Reduce the lead time for change
|
|
50
|
+
- 💰 Reduce the cost of API integration testing
|
|
51
|
+
- 💥 Prevent breaking changes
|
|
52
|
+
- 🔎 Understand your system usage
|
|
53
|
+
- 📃 Document your APIs for free
|
|
54
|
+
- 🗄 Remove the need for complex data fixtures
|
|
55
|
+
- 🤷♂️ Reduce the reliance on complex test environments
|
|
306
56
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
<details><summary>Verification Options</summary>
|
|
310
|
-
|
|
311
|
-
| Parameter | Required? | Type | Description |
|
|
312
|
-
| --------------------------- | --------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
313
|
-
| `providerBaseUrl` | true | string | Running API provider host endpoint. |
|
|
314
|
-
| `pactBrokerUrl` | false | string | Base URL of the Pact Broker from which to retrieve the pacts. Required if `pactUrls` not given. |
|
|
315
|
-
| `provider` | false | string | Name of the provider if fetching from a Broker |
|
|
316
|
-
| `consumerVersionSelectors` | false | ConsumerVersionSelector\|array | Use [Selectors](https://docs.pact.io/selectors) to is a way we specify which pacticipants and versions we want to use when configuring verifications. |
|
|
317
|
-
| `consumerVersionTag` | false | string\|array | Retrieve the latest pacts with given tag(s) |
|
|
318
|
-
| `providerVersionTag` | false | string\|array | Tag(s) to apply to the provider application |
|
|
319
|
-
| `includeWipPactsSince` | false | string | Includes pact marked as WIP since this date. String in the format %Y-%m-%d or %Y-%m-%dT%H:%M:%S.000%:z |
|
|
320
|
-
| `pactUrls` | false | array | Array of local pact file paths or HTTP-based URLs. Required if _not_ using a Pact Broker. |
|
|
321
|
-
| `providerStatesSetupUrl` | false | string | Deprecated (use URL to send PUT requests to setup a given provider state |
|
|
322
|
-
| `stateHandlers` | false | object | Map of "state" to a function that sets up a given provider state. See docs below for more information |
|
|
323
|
-
| `requestFilter` | false | function | Function that may be used to alter the incoming request or outgoing response from the verification process. See belot for use. |
|
|
324
|
-
| `pactBrokerUsername` | false | string | Username for Pact Broker basic authentication |
|
|
325
|
-
| `pactBrokerPassword` | false | string | Password for Pact Broker basic authentication |
|
|
326
|
-
| `pactBrokerToken` | false | string | Bearer token for Pact Broker authentication |
|
|
327
|
-
| `publishVerificationResult` | false | boolean | Publish verification result to Broker (_NOTE_: you should only enable this during CI builds) |
|
|
328
|
-
| `customProviderHeaders` | false | array | Header(s) to add to provider state set up and pact verification | | `requests`. eg 'Authorization: Basic cGFjdDpwYWN0'. |
|
|
329
|
-
| `providerVersion` | false | string | Provider version, required to publish verification result to Broker. Optional otherwise. |
|
|
330
|
-
| `enablePending` | false | boolean | Enable the [pending pacts](https://docs.pact.io/pending) feature. |
|
|
331
|
-
| `timeout` | false | number | The duration in ms we should wait to confirm verification process was successful. Defaults to 30000. |
|
|
332
|
-
| `format` | false | string | What format the verification results are printed in. Options are `json`, `xml`, `progress` and `RspecJunitFormatter` (which is a synonym for `xml`) |
|
|
333
|
-
| `verbose` | false | boolean | Enables verbose output for underlying pact binary. |
|
|
57
|
+
Watch our [series](https://www.youtube.com/playlist?list=PLwy9Bnco-IpfZ72VQ7hce8GicVZs7nm0i) on the problems with end-to-end integrated tests, and how contract testing can help.
|
|
334
58
|
|
|
335
|
-
</
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
</table>
|
|
336
62
|
|
|
337
|
-
|
|
63
|
+

|
|
338
64
|
|
|
339
|
-
|
|
340
|
-
let opts = {
|
|
341
|
-
pactBroker: "http://my-broker",
|
|
342
|
-
provider: "Animal Profile Service",
|
|
343
|
-
consumerVersionTag: ["master", "prod"],
|
|
344
|
-
}
|
|
345
|
-
```
|
|
65
|
+
## Documentation
|
|
346
66
|
|
|
347
|
-
|
|
67
|
+
This readme offers an basic introduction to the library. The full documentation for Pact JS and the rest of the framework is available at https://docs.pact.io/.
|
|
348
68
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
69
|
+
- [Installation](#installation)
|
|
70
|
+
- [Consumer Testing](/docs/consumer.md)
|
|
71
|
+
- [Matching](/docs/matching.md)
|
|
72
|
+
- [Provider Testing](/docs/provider.md)
|
|
73
|
+
- [Event Driven Systems](/docs/messages.md)
|
|
74
|
+
- [GraphQL](/docs/graphql.md)
|
|
75
|
+
- [XML](/docs/xml.md)
|
|
76
|
+
- [Examples](https://github.com/pact-foundation/pact-js/tree/master/examples/)
|
|
77
|
+
- [Migration guide](/MIGRATION.md)
|
|
78
|
+
- [Troubleshooting](/docs/troubleshooting.md)
|
|
354
79
|
|
|
355
|
-
|
|
80
|
+
### Tutorial (60 minutes)
|
|
356
81
|
|
|
357
|
-
|
|
358
|
-
let opts = {
|
|
359
|
-
publishVerificationResult: true, //generally you'd do something like `process.env.CI === 'true'`
|
|
360
|
-
providerVersion: "version", //recommended to be the git sha
|
|
361
|
-
providerVersionTag: "tag", //optional, recommended to be the git branch
|
|
362
|
-
}
|
|
363
|
-
```
|
|
82
|
+
Learn the key Pact JS features in 60 minutes: https://github.com/pact-foundation/pact-workshop-js
|
|
364
83
|
|
|
365
|
-
|
|
84
|
+
## Need Help
|
|
366
85
|
|
|
367
|
-
|
|
86
|
+
- [Join](http://slack.pact.io) our community [slack workspace](http://pact-foundation.slack.com/).
|
|
87
|
+
- Stack Overflow: https://stackoverflow.com/questions/tagged/pact
|
|
88
|
+
- Say 👋 on Twitter: [@pact_up]
|
|
368
89
|
|
|
369
|
-
|
|
90
|
+
## Installation
|
|
370
91
|
|
|
371
|
-
|
|
92
|
+
```shell
|
|
93
|
+
npm i -S @pact-foundation/pact@latest
|
|
372
94
|
|
|
373
|
-
|
|
374
|
-
let opts = {
|
|
375
|
-
...
|
|
376
|
-
stateHandlers: {
|
|
377
|
-
"Has no animals": () => {
|
|
378
|
-
animalRepository.clear()
|
|
379
|
-
return Promise.resolve(`Animals removed from the db`)
|
|
380
|
-
},
|
|
381
|
-
"Has some animals": () => {
|
|
382
|
-
importData()
|
|
383
|
-
return Promise.resolve(`Animals added to the db`)
|
|
384
|
-
},
|
|
385
|
-
"Has an animal with ID 1": () => {
|
|
386
|
-
importData()
|
|
387
|
-
return Promise.resolve(`Animals added to the db`)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return new Verifier(opts).verifyProvider().then(...)
|
|
95
|
+
# 🚀 now write some tests!
|
|
393
96
|
```
|
|
394
97
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
Read more about [Provider States](https://docs.pact.io/getting_started/provider_states).
|
|
398
|
-
|
|
399
|
-
#### Pending Pacts
|
|
400
|
-
|
|
401
|
-
_NOTE_: This feature is currently only available on [Pactflow]
|
|
402
|
-
|
|
403
|
-
Pending pacts is a feature that allows consumers to publish new contracts or changes to existing contracts without breaking Provider's builds. It does so by flagging the contract as "unverified" in the Pact Broker the first time a contract is published. A Provider can then enable a behaviour (via `enablePending: true`) that will still perform a verification (and thus share the results back to the broker) but _not_ fail the verification step itself.
|
|
404
|
-
|
|
405
|
-
This enables safe introduction of new contracts into the system, without breaking Provider builds, whilst still providing feedback to Consumers as per before.
|
|
406
|
-
|
|
407
|
-
See the [docs](https://docs.pact.io/pending) and this [article](http://blog.pact.io/2020/02/24/how-we-have-fixed-the-biggest-problem-with-the-pact-workflow/) for more background.
|
|
408
|
-
|
|
409
|
-
#### WIP Pacts
|
|
98
|
+
Looking for the previous [stable 9.x.x release](https://github.com/pact-foundation/pact-js/tree/9.x.x)?
|
|
410
99
|
|
|
411
|
-
|
|
100
|
+
*NOTE: Make sure the `ignore-scripts` option is disabled, pact uses npm scripts to compile native dependencies and won't function without it.*
|
|
412
101
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
See the [docs](https://docs.pact.io/wip) and this [article](http://blog.pact.io/2020/02/24/introducing-wip-pacts/) for more background.
|
|
416
|
-
|
|
417
|
-
#### Verifying multiple contracts with the same tag (e.g. for Mobile use cases)
|
|
102
|
+
### Do Not Track
|
|
418
103
|
|
|
419
|
-
|
|
104
|
+
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://docs.pact.io/metrics) are your type of OS, and the version information for the package being installed. No PII data is sent as part of this request. You can disable tracking by setting the environment variable `PACT_DO_NOT_TRACK=1`:
|
|
420
105
|
|
|
421
|
-
|
|
106
|
+

|
|
422
107
|
|
|
423
|
-
|
|
424
|
-
consumerVersionSelectors: [
|
|
425
|
-
{
|
|
426
|
-
tag: "prod",
|
|
427
|
-
all: true,
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
tag: "master",
|
|
431
|
-
latest: true,
|
|
432
|
-
},
|
|
433
|
-
]
|
|
434
|
-
```
|
|
108
|
+
## Usage
|
|
435
109
|
|
|
436
|
-
|
|
110
|
+
### Consumer package
|
|
437
111
|
|
|
438
|
-
|
|
112
|
+
The main consumer interface are the `PactV3` class and `MatchersV3` exports of the `@pact-foundation/pact` package.
|
|
439
113
|
|
|
440
|
-
|
|
114
|
+
#### Writing a Consumer test
|
|
441
115
|
|
|
442
|
-
|
|
116
|
+
Pact is a consumer-driven contract testing tool, which is a fancy way of saying that the API `Consumer` writes a test to set out its assumptions and needs of its API `Provider`(s). By unit testing our API client with Pact, it will produce a `contract` that we can share to our `Provider` to confirm these assumptions and prevent breaking changes.
|
|
443
117
|
|
|
444
|
-
|
|
445
|
-
2. the ability to modify a request/response and modify the payload. The flag to achieve this is `requestFilter`.
|
|
118
|
+
In this example, we are going to be testing our User API client, responsible for communicating with the `UserAPI` over HTTP. It currently has a single method `GetUser(id)` that will return a `*User`.
|
|
446
119
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
For example, to have an `Authorization` bearer token header sent as part of the verification request, set the `verifyProvider` options as per below:
|
|
120
|
+
Pact tests have a few key properties. We'll demonstrate a common example using the 3A `Arrange/Act/Assert` pattern.
|
|
450
121
|
|
|
451
122
|
```js
|
|
452
|
-
|
|
453
|
-
let opts = {
|
|
454
|
-
provider: 'Animal Profile Service',
|
|
455
|
-
...
|
|
456
|
-
stateHandlers: {
|
|
457
|
-
"is authenticated": () => {
|
|
458
|
-
token = "1234"
|
|
459
|
-
Promise.resolve(`Valid bearer token generated`)
|
|
460
|
-
},
|
|
461
|
-
"is not authenticated": () => {
|
|
462
|
-
token = ""
|
|
463
|
-
Promise.resolve(`Expired bearer token generated`)
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
// this middleware is executed for each request, allowing `token` to change between invocations
|
|
468
|
-
// it is common to pair this with `stateHandlers` as per above, that can set/expire the token
|
|
469
|
-
// for different test cases
|
|
470
|
-
requestFilter: (req, res, next) => {
|
|
471
|
-
req.headers["Authorization"] = `Bearer: ${token}`
|
|
472
|
-
next()
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
// This header will always be sent for each and every request, and can't be dynamic
|
|
476
|
-
// (i.e. passing a variable instead of the bearer token)
|
|
477
|
-
customProviderHeaders: ["Authorization: Bearer 1234"]
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return new Verifier(opts).verifyProvider().then(...)
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
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.
|
|
484
|
-
|
|
485
|
-
_Important Note_: You should only use this feature for things that can not be persisted in the pact file. By modifying the request, you are potentially modifying the contract from the consumer tests!
|
|
123
|
+
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
|
|
486
124
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
- versions your contracts
|
|
494
|
-
- tells you which versions of your applications can be deployed safely together
|
|
495
|
-
- allows you to deploy your services independently
|
|
496
|
-
- provides API documentation of your applications that is guaranteed to be up-to date
|
|
497
|
-
- visualises the relationships between your services
|
|
498
|
-
- integrates with other systems, such as Slack or your CI server, via webhooks
|
|
499
|
-
- ...and much much [more](https://docs.pact.io/getting_started/sharing_pacts).
|
|
500
|
-
|
|
501
|
-
[Host your own](https://github.com/pact-foundation/pact_broker), or signup for a free hosted [Pact Broker](https://pactflow.io).
|
|
125
|
+
// Create a 'pact' between the two applications in the integration we are testing
|
|
126
|
+
const provider = new PactV3({
|
|
127
|
+
dir: path.resolve(process.cwd(), 'pacts'),
|
|
128
|
+
consumer: 'MyConsumer',
|
|
129
|
+
provider: 'MyProvider',
|
|
130
|
+
});
|
|
502
131
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
132
|
+
// API Client that will fetch dogs from the Dog API
|
|
133
|
+
// This is the target of our Pact test
|
|
134
|
+
public getMeDogs = (from: string): AxiosPromise => {
|
|
135
|
+
return axios.request({
|
|
136
|
+
baseURL: this.url,
|
|
137
|
+
params: { from },
|
|
138
|
+
headers: { Accept: 'application/json' },
|
|
139
|
+
method: 'GET',
|
|
140
|
+
url: '/dogs',
|
|
141
|
+
});
|
|
507
142
|
};
|
|
508
143
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
| `pactBrokerToken` | `false` | string | Bearer token for Pact Broker authentication. Optional. If using Pactflow, you likely need this option |
|
|
526
|
-
| `pactBrokerUsername` | `false` | string | Username for Pact Broker basic authentication. Optional. If using Pactflow, you most likely need to use `pactBrokerToken` |
|
|
527
|
-
| `pactBrokerPassword` | `false` | string | Password for Pact Broker basic authentication. Optional. If using Pactflow, you most likely need to use `pactBrokerToken` |
|
|
528
|
-
| `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)) |
|
|
529
|
-
| `tags` | `false` | array of strings | Tag your pacts, often used with your branching, release or environment strategy e.g. ['prod', 'test'] |
|
|
530
|
-
|
|
531
|
-
</details>
|
|
532
|
-
|
|
533
|
-
If your broker has a self signed certificate, set the environment variable `SSL_CERT_FILE` (or `SSL_CERT_DIR`) pointing to a copy of your certificate.
|
|
534
|
-
|
|
535
|
-
#### Publishing Verification Results to a Pact Broker
|
|
536
|
-
|
|
537
|
-
If you're using a Pact Broker (e.g. a hosted one at https://pactflow.io), you can
|
|
538
|
-
publish your verification results so that consumers can query if they are safe
|
|
539
|
-
to release.
|
|
540
|
-
|
|
541
|
-
It looks like this:
|
|
542
|
-
|
|
543
|
-

|
|
544
|
-
|
|
545
|
-
To publish the verification results back to the Pact Broker, you need to enable the 'publish' flag, set the provider version and optional provider version tags:
|
|
546
|
-
|
|
547
|
-
```js
|
|
548
|
-
let opts = {
|
|
549
|
-
publishVerificationResult: true, //recommended to only publish from CI by setting the value to `process.env.CI === 'true'`
|
|
550
|
-
providerVersion: "version", //recommended to be the git sha eg. process.env.MY_CI_COMMIT
|
|
551
|
-
providerVersionTag: "tag", //optional, recommended to be the git branch eg. process.env.MY_CI_BRANCH
|
|
552
|
-
}
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
## Asynchronous API Testing
|
|
556
|
-
|
|
557
|
-
_Since version `v6.0.0` or later_
|
|
558
|
-
|
|
559
|
-
Modern distributed architectures are increasingly integrated in a decoupled, asynchronous fashion. Message queues such as ActiveMQ, RabbitMQ, SQS, Kafka and Kinesis are common, often integrated via small and frequent numbers of microservices (e.g. lambda.).
|
|
560
|
-
|
|
561
|
-
Furthermore, the web has things like WebSockets which involve bidirectional messaging.
|
|
562
|
-
|
|
563
|
-
Pact supports these use cases, by abstracting away the protocol and focussing on the messages passing between them.
|
|
564
|
-
|
|
565
|
-
For further reading and introduction into this topic, see this [article](https://dius.com.au/2017/09/22/contract-testing-serverless-and-asynchronous-applications/)
|
|
566
|
-
and our [asynchronous examples](#asynchronous-apis) for a more detailed overview of these concepts.
|
|
567
|
-
|
|
568
|
-
### Consumer
|
|
569
|
-
|
|
570
|
-
A Consumer is the system that will be reading a message from a queue or some other intermediary - like a DynamoDB table or S3 bucket -
|
|
571
|
-
and be able to handle it.
|
|
572
|
-
|
|
573
|
-
From a Pact testing point of view, Pact takes the place of the intermediary (MQ/broker etc.) and confirms whether or not the consumer is able to handle a request.
|
|
574
|
-
|
|
575
|
-
The following test creates a contract for a Dog API handler:
|
|
576
|
-
|
|
577
|
-
```js
|
|
578
|
-
const {
|
|
579
|
-
MessageConsumerPact,
|
|
580
|
-
synchronousBodyHandler,
|
|
581
|
-
} = require("@pact-foundation/pact")
|
|
582
|
-
|
|
583
|
-
// 1 Dog API Handler
|
|
584
|
-
const dogApiHandler = function (dog) {
|
|
585
|
-
if (!dog.id && !dog.name && !dog.type) {
|
|
586
|
-
throw new Error("missing fields")
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// do some other things to dog...
|
|
590
|
-
// e.g. dogRepository.save(dog)
|
|
591
|
-
return
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// 2 Pact Message Consumer
|
|
595
|
-
const messagePact = new MessageConsumerPact({
|
|
596
|
-
consumer: "MyJSMessageConsumer",
|
|
597
|
-
dir: path.resolve(process.cwd(), "pacts"),
|
|
598
|
-
pactfileWriteMode: "update",
|
|
599
|
-
provider: "MyJSMessageProvider",
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
describe("receive dog event", () => {
|
|
603
|
-
it("accepts a valid dog", () => {
|
|
604
|
-
// 3 Consumer expectations
|
|
605
|
-
return (
|
|
606
|
-
messagePact
|
|
607
|
-
.given("some state")
|
|
608
|
-
.expectsToReceive("a request for a dog")
|
|
609
|
-
.withContent({
|
|
610
|
-
id: like(1),
|
|
611
|
-
name: like("rover"),
|
|
612
|
-
type: term({ generate: "bulldog", matcher: "^(bulldog|sheepdog)$" }),
|
|
613
|
-
})
|
|
614
|
-
.withMetadata({
|
|
615
|
-
"content-type": "application/json",
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
// 4 Verify consumers' ability to handle messages
|
|
619
|
-
.verify(synchronousBodyHandler(dogApiHandler))
|
|
620
|
-
)
|
|
621
|
-
})
|
|
622
|
-
})
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
**Explanation**:
|
|
626
|
-
|
|
627
|
-
1. The Dog API - a contrived API handler example. Expects a dog object and throws an `Error` if it can't handle it.
|
|
628
|
-
- In most applications, some form of transactionality exists and communication with a MQ/broker happens.
|
|
629
|
-
- It's important we separate out the protocol bits from the message handling bits, so that we can test that in isolation.
|
|
630
|
-
1. Creates the MessageConsumer class
|
|
631
|
-
1. Setup the expectations for the consumer - here we expect a `dog` object with three fields
|
|
632
|
-
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:
|
|
633
|
-
- 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.
|
|
634
|
-
- 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.
|
|
635
|
-
- In this case, we wrap the actual dogApiHandler with a convenience function `synchronousBodyHandler` provided by Pact, which Promisifies the handler and extracts the contents.
|
|
636
|
-
|
|
637
|
-
### Provider (Producer)
|
|
638
|
-
|
|
639
|
-
A Provider (Producer in messaging parlance) is the system that will be putting a message onto the queue.
|
|
640
|
-
|
|
641
|
-
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.
|
|
642
|
-
|
|
643
|
-
```js
|
|
644
|
-
const { MessageProviderPact } = require("@pact-foundation/pact")
|
|
645
|
-
|
|
646
|
-
// 1 Messaging integration client
|
|
647
|
-
const dogApiClient = {
|
|
648
|
-
createDog: () => {
|
|
649
|
-
return new Promise((resolve, reject) => {
|
|
650
|
-
resolve({
|
|
651
|
-
id: 1,
|
|
652
|
-
name: "fido",
|
|
653
|
-
type: "bulldog",
|
|
144
|
+
const dogExample = { dog: 1 };
|
|
145
|
+
const EXPECTED_BODY = MatchersV3.eachLike(dogExample);
|
|
146
|
+
|
|
147
|
+
describe('GET /dogs', () => {
|
|
148
|
+
it('returns an HTTP 200 and a list of docs', () => {
|
|
149
|
+
// Arrange: Setup our expected interactions
|
|
150
|
+
//
|
|
151
|
+
// We use Pact to mock out the backend API
|
|
152
|
+
provider
|
|
153
|
+
.given('I have a list of dogs')
|
|
154
|
+
.uponReceiving('a request for all dogs with the builder pattern')
|
|
155
|
+
.withRequest({
|
|
156
|
+
method: 'GET',
|
|
157
|
+
path: '/dogs',
|
|
158
|
+
query: { from: 'today' },
|
|
159
|
+
headers: { Accept: 'application/json' },
|
|
654
160
|
})
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// 3 Verify the interactions
|
|
677
|
-
describe("Dog API Client", () => {
|
|
678
|
-
it("sends some dogs", () => {
|
|
679
|
-
return p.verify()
|
|
680
|
-
})
|
|
681
|
-
})
|
|
682
|
-
})
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**Explanation**:
|
|
686
|
-
|
|
687
|
-
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
|
|
688
|
-
1. We configure Pact to stand-in for the queue. The most important bit here is the `messageProviders` block
|
|
689
|
-
- 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.
|
|
690
|
-
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.
|
|
691
|
-
|
|
692
|
-
### Pact Broker Integration
|
|
693
|
-
|
|
694
|
-
As per HTTP APIs, you can [publish contracts and verification results to a Broker](#publishing-pacts-to-a-broker).
|
|
695
|
-
|
|
696
|
-
## Matching
|
|
697
|
-
|
|
698
|
-
Matching makes your tests more expressive making your tests less brittle.
|
|
699
|
-
|
|
700
|
-
Rather than use hard-coded values which must then be present on the Provider side,
|
|
701
|
-
you can use regular expressions and type matches on objects and arrays to validate the
|
|
702
|
-
structure of your APIs.
|
|
703
|
-
|
|
704
|
-
_NOTE: Make sure to start the mock service via the `Pact` declaration with the option `specification: 2` to get access to these features._
|
|
705
|
-
|
|
706
|
-
### Match common formats
|
|
707
|
-
|
|
708
|
-
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:
|
|
709
|
-
|
|
710
|
-
<details><summary>Matchers API</summary>
|
|
711
|
-
|
|
712
|
-
| method | description |
|
|
713
|
-
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
714
|
-
| `boolean` | Match a boolean value (using equality) |
|
|
715
|
-
| `string` | Match a string value |
|
|
716
|
-
| `integer` | Will match all numbers that are integers (both ints and longs) |
|
|
717
|
-
| `decimal` | Will match all real numbers (floating point and decimal) |
|
|
718
|
-
| `hexadecimal` | Will match all hexadecimal encoded strings |
|
|
719
|
-
| `iso8601Date` | Will match string containing basic ISO8601 dates (e.g. 2016-01-01) |
|
|
720
|
-
| `iso8601DateTime` | Will match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00) |
|
|
721
|
-
| `iso8601DateTimeWithMillis` | Will match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00) |
|
|
722
|
-
| `rfc3339Timestamp` | Will match a string containing an RFC3339 formatted timestapm (e.g. Mon, 31 Oct 2016 15:21:41 -0400) |
|
|
723
|
-
| `iso8601Time` | Will match string containing times (e.g. T22:44:30.652Z) |
|
|
724
|
-
| `ipv4Address` | Will match string containing IP4 formatted address |
|
|
725
|
-
| `ipv6Address` | Will match string containing IP6 formatted address |
|
|
726
|
-
| `uuid` | Will match strings containing UUIDs |
|
|
727
|
-
| `email` | Will match strings containing Email address |
|
|
728
|
-
|
|
729
|
-
</details>
|
|
730
|
-
|
|
731
|
-
### Match based on type
|
|
732
|
-
|
|
733
|
-
```javascript
|
|
734
|
-
const { like, string } = Matchers
|
|
735
|
-
|
|
736
|
-
provider.addInteraction({
|
|
737
|
-
state: "Has some animals",
|
|
738
|
-
uponReceiving: "a request for an animal",
|
|
739
|
-
withRequest: {
|
|
740
|
-
method: "GET",
|
|
741
|
-
path: "/animals/1",
|
|
742
|
-
},
|
|
743
|
-
willRespondWith: {
|
|
744
|
-
status: 200,
|
|
745
|
-
headers: {
|
|
746
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
747
|
-
},
|
|
748
|
-
body: {
|
|
749
|
-
id: 1,
|
|
750
|
-
name: string("Billy"),
|
|
751
|
-
address: like({
|
|
752
|
-
street: "123 Smith St",
|
|
753
|
-
suburb: "Smithsville",
|
|
754
|
-
postcode: 7777,
|
|
755
|
-
}),
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
})
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
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`.
|
|
762
|
-
|
|
763
|
-
[flexible-matching]: https://github.com/realestate-com-au/pact/wiki/Regular-expressions-and-type-matching-with-Pact
|
|
764
|
-
|
|
765
|
-
### Match based on arrays
|
|
766
|
-
|
|
767
|
-
Matching provides the ability to specify flexible length arrays. For example:
|
|
768
|
-
|
|
769
|
-
```javascript
|
|
770
|
-
pact.eachLike(obj, { min: 3 })
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
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.
|
|
774
|
-
|
|
775
|
-
Below is an example that uses all of the Pact Matchers.
|
|
776
|
-
|
|
777
|
-
```javascript
|
|
778
|
-
const { somethingLike: like, term, eachLike } = pact
|
|
779
|
-
|
|
780
|
-
const animalBodyExpectation = {
|
|
781
|
-
id: 1,
|
|
782
|
-
first_name: "Billy",
|
|
783
|
-
last_name: "Goat",
|
|
784
|
-
animal: "goat",
|
|
785
|
-
age: 21,
|
|
786
|
-
gender: term({
|
|
787
|
-
matcher: "F|M",
|
|
788
|
-
generate: "M",
|
|
789
|
-
}),
|
|
790
|
-
location: {
|
|
791
|
-
description: "Melbourne Zoo",
|
|
792
|
-
country: "Australia",
|
|
793
|
-
post_code: 3000,
|
|
794
|
-
},
|
|
795
|
-
eligibility: {
|
|
796
|
-
available: true,
|
|
797
|
-
previously_married: false,
|
|
798
|
-
},
|
|
799
|
-
children: eachLike({ name: "Sally", age: 2 }),
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// Define animal list payload, reusing existing object matcher
|
|
803
|
-
// Note that using eachLike ensure that all values are matched by type
|
|
804
|
-
const animalListExpectation = eachLike(animalBodyExpectation, {
|
|
805
|
-
min: MIN_ANIMALS,
|
|
806
|
-
})
|
|
807
|
-
|
|
808
|
-
provider.addInteraction({
|
|
809
|
-
state: "Has some animals",
|
|
810
|
-
uponReceiving: "a request for all animals",
|
|
811
|
-
withRequest: {
|
|
812
|
-
method: "GET",
|
|
813
|
-
path: "/animals/available",
|
|
814
|
-
},
|
|
815
|
-
willRespondWith: {
|
|
816
|
-
status: 200,
|
|
817
|
-
headers: {
|
|
818
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
819
|
-
},
|
|
820
|
-
body: animalListExpectation,
|
|
821
|
-
},
|
|
822
|
-
})
|
|
823
|
-
```
|
|
824
|
-
|
|
825
|
-
### Match by regular expression
|
|
826
|
-
|
|
827
|
-
If none of the above matchers or formats work, you can write your own regex matcher.
|
|
828
|
-
|
|
829
|
-
The underlying mock service is written in Ruby, so the regular expression must be in a Ruby format, not a Javascript format.
|
|
830
|
-
|
|
831
|
-
```javascript
|
|
832
|
-
const { term } = pact
|
|
833
|
-
|
|
834
|
-
provider.addInteraction({
|
|
835
|
-
state: "Has some animals",
|
|
836
|
-
uponReceiving: "a request for an animal",
|
|
837
|
-
withRequest: {
|
|
838
|
-
method: "GET",
|
|
839
|
-
path: "/animals/1",
|
|
840
|
-
},
|
|
841
|
-
willRespondWith: {
|
|
842
|
-
status: 200,
|
|
843
|
-
headers: {
|
|
844
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
845
|
-
},
|
|
846
|
-
body: {
|
|
847
|
-
id: 100,
|
|
848
|
-
name: "billy",
|
|
849
|
-
gender: term({
|
|
850
|
-
matcher: "F|M",
|
|
851
|
-
generate: "F",
|
|
852
|
-
}),
|
|
853
|
-
},
|
|
854
|
-
},
|
|
855
|
-
})
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
## GraphQL API
|
|
859
|
-
|
|
860
|
-
GraphQL is simply an abstraction over HTTP and may be tested via Pact. There are two wrapper APIs available for GraphQL specific testing: `GraphQLInteraction` and `ApolloGraphQLInteraction`.
|
|
861
|
-
|
|
862
|
-
These are both lightweight wrappers over the standard DSL in order to make GraphQL testing a bit nicer.
|
|
863
|
-
|
|
864
|
-
See the [history](https://github.com/pact-foundation/pact-js/issues/254#issuecomment-442185695), and below for an example.
|
|
865
|
-
|
|
866
|
-
## Tutorial (60 minutes)
|
|
867
|
-
|
|
868
|
-
Learn everything in Pact JS in 60 minutes: https://github.com/pact-foundation/pact-workshop-js.
|
|
869
|
-
|
|
870
|
-
The workshop takes you through all of the key concepts using a React consumer and an Express API.
|
|
871
|
-
|
|
872
|
-
## Examples
|
|
873
|
-
|
|
874
|
-
### HTTP APIs
|
|
875
|
-
|
|
876
|
-
- [Complete Example (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/e2e)
|
|
877
|
-
- [Pact with AVA (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/ava)
|
|
878
|
-
- [Pact with Jest (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/jest)
|
|
879
|
-
- [Pact with TypeScript + Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/typescript)
|
|
880
|
-
- [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha)
|
|
881
|
-
- [Pact with GraphQL](https://github.com/pact-foundation/pact-js/tree/master/examples/graphql)
|
|
882
|
-
- [Pact with Karma + Jasmine](https://github.com/pact-foundation/pact-js/tree/master/karma/jasmine)
|
|
883
|
-
- [Pact with Karma + Mocha](https://github.com/pact-foundation/pact-js/tree/master/karma/mocha)
|
|
884
|
-
- [Pact with React + Jest](https://github.com/pact-foundation/pact-workshop-js)
|
|
885
|
-
|
|
886
|
-
### Asynchronous APIs
|
|
887
|
-
|
|
888
|
-
- [Asynchronous messages](https://github.com/pact-foundation/pact-js/tree/master/examples/messages)
|
|
889
|
-
- [Serverless](https://github.com/pact-foundation/pact-js/tree/master/examples/serverless)
|
|
890
|
-
|
|
891
|
-
## Using Pact in non-Node environments
|
|
892
|
-
|
|
893
|
-
Pact requires a Node runtime to be able to start and stop Mock servers, write logs and other things.
|
|
894
|
-
|
|
895
|
-
However, when used within browser or non-Node based environments - such as with Karma or ng-test - this is not possible.
|
|
896
|
-
|
|
897
|
-
To address this challenge, we have released a separate 'web' based module for this purpose - `pact-web`.
|
|
898
|
-
Whilst it still provides a testing DSL, it cannot start and stop mock servers as per the `pact`
|
|
899
|
-
package, so you will need to coordinate this yourself prior to and after executing any tests.
|
|
900
|
-
|
|
901
|
-
To get started, install `pact-web` and [Pact Node](https://github.com/pact-foundation/pact-node):
|
|
902
|
-
|
|
903
|
-
npm install --save-dev @pact-foundation/pact-web @pact-foundation/pact-node
|
|
904
|
-
|
|
905
|
-
If you're not using Karma, you can start and stop the mock server using [Pact Node](https://github.com/pact-foundation/pact-node) or something like [Grunt Pact](https://github.com/pact-foundation/grunt-pact).
|
|
906
|
-
|
|
907
|
-
### Using Pact with Karma
|
|
908
|
-
|
|
909
|
-
We have create a [plugin](https://github.com/pact-foundation/karma-pact) for Karma,
|
|
910
|
-
which will automatically start and stop any Mock Server for your Pact tests.
|
|
911
|
-
|
|
912
|
-
Modify your `karma.conf.js` file as per below to get started:
|
|
913
|
-
|
|
914
|
-
```js
|
|
915
|
-
// Load pact framework - this will start/stop mock server automatically
|
|
916
|
-
frameworks: ['pact'],
|
|
917
|
-
|
|
918
|
-
// Load the pact and default karma plugins
|
|
919
|
-
plugins: [
|
|
920
|
-
'karma-*',
|
|
921
|
-
'@pact-foundation/karma-pact'
|
|
922
|
-
],
|
|
923
|
-
|
|
924
|
-
// load pact web module
|
|
925
|
-
files: [
|
|
926
|
-
'node_modules/@pact-foundation/pact-web/pact-web.js',
|
|
927
|
-
...
|
|
928
|
-
],
|
|
929
|
-
|
|
930
|
-
// Configure the mock service
|
|
931
|
-
pact: [{
|
|
932
|
-
port: 1234,
|
|
933
|
-
consumer: 'KarmaMochaConsumer',
|
|
934
|
-
provider: 'KarmaMochaProvider',
|
|
935
|
-
logLevel: 'DEBUG',
|
|
936
|
-
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
|
|
937
|
-
dir: path.resolve(process.cwd(), 'pacts')
|
|
938
|
-
}],
|
|
161
|
+
.willRespondWith({
|
|
162
|
+
status: 200,
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
body: EXPECTED_BODY,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return provider.executeTest((mockserver) => {
|
|
168
|
+
// Act: test our API client behaves correctly
|
|
169
|
+
//
|
|
170
|
+
// Note we configure the DogService API client dynamically to
|
|
171
|
+
// point to the mock service Pact created for us, instead of
|
|
172
|
+
// the real one
|
|
173
|
+
dogService = new DogService(mockserver.url);
|
|
174
|
+
const response = await dogService.getMeDogs('today')
|
|
175
|
+
|
|
176
|
+
// Assert: check the result
|
|
177
|
+
expect(response.data[0]).to.deep.eq(dogExample);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
939
181
|
```
|
|
940
182
|
|
|
941
|
-
|
|
183
|
+
You can see (and run) the full version of this in `./examples/v3/typescript`, as well as other exmaples in the parent folder.
|
|
942
184
|
|
|
943
|
-
|
|
185
|
+

|
|
944
186
|
|
|
945
|
-
|
|
946
|
-
like the following:
|
|
187
|
+
### Provider package
|
|
947
188
|
|
|
948
|
-
|
|
189
|
+
The main provider interface is the `Verifier` class of the `@pact-foundation/pact` package.
|
|
949
190
|
|
|
950
|
-
|
|
951
|
-
define(['client', 'Pact'], function (example, Pact) {
|
|
952
|
-
```
|
|
191
|
+
#### Verifying a Provider
|
|
953
192
|
|
|
954
|
-
|
|
193
|
+
A provider test takes one or more pact files (contracts) as input, and Pact verifies that your provider adheres to the contract. In the simplest case, you can verify a provider as per below using a local pact file, although in practice you would usually use a Pact Broker to manage your contracts and CI/CD workflow.
|
|
955
194
|
|
|
956
195
|
```js
|
|
957
|
-
require
|
|
958
|
-
baseUrl: "/base",
|
|
959
|
-
paths: {
|
|
960
|
-
Pact: "node_modules/pact-web/pact-web",
|
|
961
|
-
client: "js/client",
|
|
962
|
-
},
|
|
963
|
-
deps: allTestFiles,
|
|
964
|
-
callback: window.__karma__.start,
|
|
965
|
-
})
|
|
966
|
-
```
|
|
967
|
-
|
|
968
|
-
See this [Stack Overflow](https://stackoverflow.com/a/44170373/1008568) question for background, and
|
|
969
|
-
this [gist](https://gist.github.com/mefellows/15c9fcb052c2aa9d8951f91d48d6da54) with a working example.
|
|
970
|
-
|
|
971
|
-
## Pact JS V3
|
|
972
|
-
|
|
973
|
-
An initial beta version of Pact-JS with support for V3 specification features and XML matching has
|
|
974
|
-
been released. Current support is for Node 10, 12 and 13. Thanks to the folks at [Align Tech](https://www.aligntech.com/) for
|
|
975
|
-
sponsoring this work.
|
|
976
|
-
|
|
977
|
-
To install it:
|
|
978
|
-
|
|
979
|
-
```console
|
|
980
|
-
npm i @pact-foundation/pact@10.0.0-beta.3
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
For examples on how to use it, see [examples/v3/e2e](examples/v3/e2e) and [examples/v3/todo-consumer](examples/v3/todo-consumer).
|
|
984
|
-
|
|
985
|
-
### Using the V3 matching rules
|
|
986
|
-
|
|
987
|
-
There are a number of new matchers that can be used, like `integer` and `timestamp`. There are defined in the `MatchersV3` class that needs to be used with `PactV3` DSL.
|
|
988
|
-
|
|
989
|
-
For example:
|
|
990
|
-
|
|
991
|
-
```javascript
|
|
992
|
-
const { PactV3, MatchersV3 } = require("@pact-foundation/pact/v3")
|
|
993
|
-
const {
|
|
994
|
-
eachLike,
|
|
995
|
-
atLeastLike,
|
|
996
|
-
integer,
|
|
997
|
-
timestamp,
|
|
998
|
-
boolean,
|
|
999
|
-
string,
|
|
1000
|
-
regex,
|
|
1001
|
-
like,
|
|
1002
|
-
} = MatchersV3
|
|
1003
|
-
|
|
1004
|
-
const animalBodyExpectation = {
|
|
1005
|
-
id: integer(1),
|
|
1006
|
-
available_from: timestamp("yyyy-MM-dd'T'HH:mm:ss.SSSX"),
|
|
1007
|
-
first_name: string("Billy"),
|
|
1008
|
-
last_name: string("Goat"),
|
|
1009
|
-
animal: string("goat"),
|
|
1010
|
-
age: integer(21),
|
|
1011
|
-
gender: regex("F|M", "M"),
|
|
1012
|
-
location: {
|
|
1013
|
-
description: string("Melbourne Zoo"),
|
|
1014
|
-
country: string("Australia"),
|
|
1015
|
-
post_code: integer(3000),
|
|
1016
|
-
},
|
|
1017
|
-
eligibility: {
|
|
1018
|
-
available: boolean(true),
|
|
1019
|
-
previously_married: boolean(false),
|
|
1020
|
-
},
|
|
1021
|
-
interests: eachLike("walks in the garden/meadow"),
|
|
1022
|
-
}
|
|
1023
|
-
```
|
|
1024
|
-
|
|
1025
|
-
| Matcher | Parameters | Description |
|
|
1026
|
-
| ---------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1027
|
-
| `like` | template | Applies the `type` matcher to value, which requires values to have the same type as the template |
|
|
1028
|
-
| `eachLike` | template | Applies the `type` matcher to each value in an array, ensuring they match the template. Note that this matcher does not validate the length of the array, and the items within it |
|
|
1029
|
-
| `atLeastOneLike` | template, count: number = 1 | Behaves like the `eachLike` matcher, but also applies a minimum length validation of one on the length of the array. The optional `count` parameter controls the number of examples generated. |
|
|
1030
|
-
| `atLeastLike` | template, min: number, count?: number | Just like `atLeastOneLike`, but the minimum length is configurable. |
|
|
1031
|
-
| `atMostLike` | template, max: number, count?: number | Behaves like the `eachLike` matcher, but also applies a maximum length validation on the length of the array. The optional `count` parameter controls the number of examples generated. |
|
|
1032
|
-
| `constrainedArrayLike` | template, min: number, max: number, count?: number | Behaves like the `eachLike` matcher, but also applies a minimum and maximum length validation on the length of the array. The optional `count` parameter controls the number of examples generated. |
|
|
1033
|
-
| `boolean` | example: boolean | Matches boolean values (true, false) |
|
|
1034
|
-
| `integer` | example?: number | Value that must be an integer (must be a number and have no decimal places). If the example value is omitted, a V3 Random number generator will be used. |
|
|
1035
|
-
| `decimal` | example?: number | Value that must be a decimal number (must be a number and have at least one digit in the decimal places). If the example value is omitted, a V3 Random number generator will be used. |
|
|
1036
|
-
| `number` | example?: number | Value that must be a number. If the example value is omitted, a V3 Random number generator will be used. |
|
|
1037
|
-
| `string` | example: string | Value that must be a string. |
|
|
1038
|
-
| `regex` | pattern, example: string | Value that must match the given regular expression. |
|
|
1039
|
-
| `equal` | example | Value that must be equal to the example. This is mainly used to reset the matching rules which cascade. |
|
|
1040
|
-
| `timestamp` | format: string, example?: string | String value that must match the provided datetime format string. See [Java SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html) for details on the format string. If the example value is omitted, a value will be generated using a Timestamp generator and the current system date and time. |
|
|
1041
|
-
| `time` | format: string, example?: string | String value that must match the provided time format string. See [Java SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html) for details on the format string. If the example value is o mitted, a value will be generated using a Time generator and the current system time. |
|
|
1042
|
-
| `date` | format: string, example?: string | String value that must match the provided date format string. See [Java SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html) for details on the format string. If the example value is o mitted, a value will be generated using a Date generator and the current system date. |
|
|
1043
|
-
| `includes` | value: string | Value that must include the example value as a substring. |
|
|
1044
|
-
| `nullValue` | | Value that must be null. This will only match the JSON Null value. For other content types, it will match if the attribute is missing. |
|
|
1045
|
-
|
|
1046
|
-
### Using Pact with XML
|
|
1047
|
-
|
|
1048
|
-
You can write both consumer and provider verification tests with XML requests or responses. For an example, see [examples/v3/todo-consumer/test/consumer.spec.js](examples/v3/todo-consumer/test/consumer.spec.js).
|
|
1049
|
-
There is an `XmlBuilder` class that provides a DSL to help construct XML bodies with matching rules and generators (NOTE that generators are not supported for XML at this time).
|
|
1050
|
-
|
|
1051
|
-
for example:
|
|
1052
|
-
|
|
1053
|
-
```javascript
|
|
1054
|
-
body: new XmlBuilder("1.0", "UTF-8", "ns1:projects").build((el) => {
|
|
1055
|
-
el.setAttributes({
|
|
1056
|
-
id: "1234",
|
|
1057
|
-
"xmlns:ns1": "http://some.namespace/and/more/stuff",
|
|
1058
|
-
})
|
|
1059
|
-
el.eachLike(
|
|
1060
|
-
"ns1:project",
|
|
1061
|
-
{
|
|
1062
|
-
id: integer(1),
|
|
1063
|
-
type: "activity",
|
|
1064
|
-
name: string("Project 1"),
|
|
1065
|
-
due: timestamp("yyyy-MM-dd'T'HH:mm:ss.SZ", "2016-02-11T09:46:56.023Z"),
|
|
1066
|
-
},
|
|
1067
|
-
(project) => {
|
|
1068
|
-
project.appendElement("ns1:tasks", {}, (task) => {
|
|
1069
|
-
task.eachLike(
|
|
1070
|
-
"ns1:task",
|
|
1071
|
-
{
|
|
1072
|
-
id: integer(1),
|
|
1073
|
-
name: string("Task 1"),
|
|
1074
|
-
done: boolean(true),
|
|
1075
|
-
},
|
|
1076
|
-
null,
|
|
1077
|
-
{ examples: 5 }
|
|
1078
|
-
)
|
|
1079
|
-
})
|
|
1080
|
-
},
|
|
1081
|
-
{ examples: 2 }
|
|
1082
|
-
)
|
|
1083
|
-
})
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
|
-
### Verifying providers with VerifierV3
|
|
1087
|
-
|
|
1088
|
-
The `VerifierV3` class can verify your provider in a similar way to the existing one.
|
|
1089
|
-
|
|
1090
|
-
#### Request Filters
|
|
1091
|
-
|
|
1092
|
-
Request filters now take a request object as a parameter, and need to return the mutated one.
|
|
1093
|
-
|
|
1094
|
-
```javascript
|
|
1095
|
-
requestFilter: req => {
|
|
1096
|
-
req.headers["MY_SPECIAL_HEADER"] = "my special value"
|
|
1097
|
-
|
|
1098
|
-
// e.g. ADD Bearer token
|
|
1099
|
-
req.headers["authorization"] = `Bearer ${token}`
|
|
1100
|
-
|
|
1101
|
-
// Need to return the request back again
|
|
1102
|
-
return req
|
|
1103
|
-
},
|
|
1104
|
-
```
|
|
1105
|
-
|
|
1106
|
-
#### Provider state callbacks
|
|
1107
|
-
|
|
1108
|
-
Provider state callbacks have been updated to support parameters and return values. The first parameter is a boolean indicating whether it is a setup call
|
|
1109
|
-
(run before the verification) or a tear down call (run afterwards). The second optional parameter is a key-value map of any parameters defined in the
|
|
1110
|
-
pact file. Provider state callbacks can also return a map of key-value values. These are used with provider-state injected values, but support for that
|
|
1111
|
-
will be added in a later release.
|
|
1112
|
-
|
|
1113
|
-
```javascript
|
|
1114
|
-
stateHandlers: {
|
|
1115
|
-
"Has no animals": setup => {
|
|
1116
|
-
if (setup) {
|
|
1117
|
-
animalRepository.clear()
|
|
1118
|
-
return Promise.resolve({ description: `Animals removed to the db` })
|
|
1119
|
-
}
|
|
1120
|
-
},
|
|
1121
|
-
"Has some animals": setup => {
|
|
1122
|
-
if (setup) {
|
|
1123
|
-
importData()
|
|
1124
|
-
return Promise.resolve({
|
|
1125
|
-
description: `Animals added to the db`,
|
|
1126
|
-
count: animalRepository.count(),
|
|
1127
|
-
})
|
|
1128
|
-
}
|
|
1129
|
-
},
|
|
1130
|
-
"Has an animal with ID": (setup, parameters) => {
|
|
1131
|
-
if (setup) {
|
|
1132
|
-
importData()
|
|
1133
|
-
animalRepository.first().id = parameters.id
|
|
1134
|
-
return Promise.resolve({
|
|
1135
|
-
description: `Animal with ID ${parameters.id} added to the db`,
|
|
1136
|
-
id: parameters.id,
|
|
1137
|
-
})
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
```
|
|
1141
|
-
|
|
1142
|
-
## Troubleshooting / FAQs
|
|
1143
|
-
|
|
1144
|
-
If you are having issues, a good place to start is setting `logLevel: 'DEBUG'`
|
|
1145
|
-
when configuring the `new Pact({...})` object.
|
|
1146
|
-
|
|
1147
|
-
### Alpine + Docker
|
|
1148
|
-
|
|
1149
|
-
See https://docs.pact.io/docker/.
|
|
1150
|
-
|
|
1151
|
-
### Parallel tests
|
|
1152
|
-
|
|
1153
|
-
Pact tests are inherently stateful, as we need to keep track of the interactions on a per-test basis, to ensure each contract is validated in isolation from others. However, in larger test suites, this can result in slower test execution.
|
|
1154
|
-
|
|
1155
|
-
Modern testing frameworks like Ava and Jest support parallel execution out-of-the-box, which
|
|
1156
|
-
|
|
1157
|
-
The good news is, parallel test execution is possible, you need to ensure that:
|
|
1158
|
-
|
|
1159
|
-
1. Before any test run invocation, you remove any existing pact files, to prevent invalid / stale interactions being left over from previous test runs
|
|
1160
|
-
1. Each test is fully self-contained, with its **own mock server** on its **own port**
|
|
1161
|
-
1. You set the option `pactfileWriteMode` to `"merge"`, instructing Pact to merge any pact documents with the same consumer and provider pairing at the end of all test runs.
|
|
1162
|
-
|
|
1163
|
-
When all of your tests have completed, the result is the union of the all of the interactions from each test case in the generated pact file.
|
|
1164
|
-
|
|
1165
|
-
See the following examples for working parallel tests:
|
|
1166
|
-
|
|
1167
|
-
- [Pact with AVA (Node env)](https://github.com/pact-foundation/pact-js/tree/master/examples/ava)
|
|
1168
|
-
- [Pact with Mocha](https://github.com/pact-foundation/pact-js/tree/master/examples/mocha)
|
|
1169
|
-
|
|
1170
|
-
### Splitting tests across multiple files
|
|
1171
|
-
|
|
1172
|
-
Pact tests tend to be quite long, due to the need to be specific about request/response payloads. Often times it is nicer to be able to split your tests across multiple files for manageability.
|
|
1173
|
-
|
|
1174
|
-
You have a number of options to achieve this feat:
|
|
1175
|
-
|
|
1176
|
-
1. Consider implementing the [Parallel tests](#parallel-tests) guidelines.
|
|
1177
|
-
|
|
1178
|
-
1. Create a Pact test helper to orchestrate the setup and teardown of the mock service for multiple tests.
|
|
1179
|
-
|
|
1180
|
-
In larger test bases, this can significantly reduce test suite time and the amount of code you have to manage.
|
|
1181
|
-
|
|
1182
|
-
See this [example](https://github.com/tarciosaraiva/pact-melbjs/blob/master/helper.js) and this [issue](https://github.com/pact-foundation/pact-js/issues/11) for more.
|
|
1183
|
-
|
|
1184
|
-
1. Set `pactfileWriteMode` to `merge` in the `Pact()` constructor
|
|
1185
|
-
|
|
1186
|
-
This will allow you to have multiple independent tests for a given Consumer-Provider pair, without it clobbering previous interactions, thereby allowing you to incrementally build up or modify your pact files.
|
|
1187
|
-
|
|
1188
|
-
This feature addresses the use case of "my pact suite takes bloody ages to run, so I just want to replace the interactions that have been run in this test execution" and requires careful management
|
|
1189
|
-
|
|
1190
|
-
_NOTE_: If using this approach, you _must_ be careful to clear out existing pact files (e.g. `rm ./pacts/*.json`) before you run tests to ensure you don't have left over requests that are no longer relevant.
|
|
1191
|
-
|
|
1192
|
-
See this [PR](https://github.com/pact-foundation/pact-js/pull/48) for background.
|
|
1193
|
-
|
|
1194
|
-
### Test fails when it should pass
|
|
1195
|
-
|
|
1196
|
-
TL;DR - you almost certainly have not properly handled (returned) a Promise.
|
|
1197
|
-
|
|
1198
|
-
We see this sort of thing all of the time:
|
|
1199
|
-
|
|
1200
|
-
```
|
|
1201
|
-
it("returns a successful thing", () => {
|
|
1202
|
-
executeApiCallThatIsAPromise()
|
|
1203
|
-
.then((response) => {
|
|
1204
|
-
expect(response.data).to.eq({...})
|
|
1205
|
-
})
|
|
1206
|
-
.then(() => {
|
|
1207
|
-
provider.verify()
|
|
1208
|
-
})
|
|
1209
|
-
})
|
|
1210
|
-
```
|
|
1211
|
-
|
|
1212
|
-
There are several problems with this:
|
|
1213
|
-
|
|
1214
|
-
1. in the "returns a successful thing", the call to `executeApiCallThatIsAPromise()` is a function that returns a Promise, but is not returned by the function (`it` block) - this leaves a dangling, unhandled Promise. In your case it fails, but by the time it does the `it` block has already completed without problems - and returns a green result ✅.
|
|
1215
|
-
1. In the `then` block, the call to `provider.verify()` is also not returned, and will suffer the same fate as (1)
|
|
196
|
+
const { Verifier } = require('@pact-foundation/pact');
|
|
1216
197
|
|
|
1217
|
-
|
|
198
|
+
// (1) Start provider locally. Be sure to stub out any external dependencies
|
|
199
|
+
server.listen(8081, () => {
|
|
200
|
+
importData();
|
|
201
|
+
console.log('Animal Profile Service listening on http://localhost:8081');
|
|
202
|
+
});
|
|
1218
203
|
|
|
1219
|
-
|
|
204
|
+
// (2) Verify that the provider meets all consumer expectations
|
|
205
|
+
describe('Pact Verification', () => {
|
|
206
|
+
it('validates the expectations of Matching Service', () => {
|
|
207
|
+
let token = 'INVALID TOKEN';
|
|
1220
208
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
.then((response) => {
|
|
1225
|
-
expect(response.data).to.eq({...})
|
|
209
|
+
return new Verifier({
|
|
210
|
+
providerBaseUrl: 'http://localhost:8081', // <- location of your running provider
|
|
211
|
+
pactUrls: [ path.resolve(process.cwd(), "./pacts/SomeConsumer-SomeProvider.json") ],
|
|
1226
212
|
})
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
See above - you probably have not returned a Promise when you should have.
|
|
1234
|
-
|
|
1235
|
-
### Re-run specific verification failures
|
|
1236
|
-
|
|
1237
|
-
If you prefix your test command (e.g. `npm t`) with the following two environment variables, you can selectively run a specific interaction during provider verification.
|
|
1238
|
-
|
|
1239
|
-
For the e2e example, let's assume we have the following failure:
|
|
1240
|
-
|
|
1241
|
-
```sh
|
|
1242
|
-
3 interactions, 2 failures
|
|
1243
|
-
|
|
1244
|
-
Failed interactions:
|
|
1245
|
-
|
|
1246
|
-
* A request for all animals given Has some animals
|
|
1247
|
-
|
|
1248
|
-
* A request for an animal with id 1 given Has an animal with ID 1
|
|
1249
|
-
```
|
|
1250
|
-
|
|
1251
|
-
If we wanted to target the second failure, we can extract the description and state as the bits before and after the word "given":
|
|
1252
|
-
|
|
1253
|
-
```sh
|
|
1254
|
-
PACT_DESCRIPTION="a request for an animal with ID 1" PACT_PROVIDER_STATE="Has an animal with ID 1" npm t
|
|
1255
|
-
```
|
|
1256
|
-
|
|
1257
|
-
Also note that `PACT_DESCRIPTION` is the failing `description` and `PACT_PROVIDER_STATE` is the corresponding `providerState` from the pact file itself.
|
|
1258
|
-
|
|
1259
|
-
### Timeout
|
|
1260
|
-
|
|
1261
|
-
Under the hood, Pact JS spins up a [Ruby Mock Service](https://github.com/pact-foundation/pact-mock-service-npm).
|
|
1262
|
-
On some systems, this may take more than a few seconds to start. It is recommended
|
|
1263
|
-
to review your unit testing timeout to ensure it has sufficient time to start the server.
|
|
1264
|
-
|
|
1265
|
-
See [here](http://stackoverflow.com/questions/42496401/all-pact-js-tests-are-failing-with-same-errors/42518752) for more details.
|
|
1266
|
-
|
|
1267
|
-
### Usage with Jest
|
|
1268
|
-
|
|
1269
|
-
Jest uses JSDOM under the hood which may cause issues with libraries making HTTP request.
|
|
1270
|
-
|
|
1271
|
-
You'll need to add the following snippet to your `package.json` to ensure it uses
|
|
1272
|
-
the proper Node environment:
|
|
213
|
+
.verifyProvider()
|
|
214
|
+
.then(() => {
|
|
215
|
+
console.log('Pact Verification Complete!');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
1273
219
|
|
|
1274
|
-
```js
|
|
1275
|
-
"jest": {
|
|
1276
|
-
"testEnvironment": "node"
|
|
1277
|
-
}
|
|
1278
220
|
```
|
|
1279
221
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
```js
|
|
1283
|
-
/**
|
|
1284
|
-
* @jest-environment node
|
|
1285
|
-
*/
|
|
1286
|
-
```
|
|
222
|
+
It's best to run Pact verification tests as part of your unit testing suite, so you can readily access stubbing, IaC and other helpful tools.
|
|
1287
223
|
|
|
1288
|
-
|
|
224
|
+

|
|
1289
225
|
|
|
1290
|
-
|
|
1291
|
-
and the Jest [example](https://github.com/pact-foundation/pact-js/blob/master/examples/jest/package.json#L10-L12) for a working example.
|
|
226
|
+
## Compatibility
|
|
1292
227
|
|
|
1293
|
-
|
|
228
|
+
<details><summary>Specification Compatibility</summary>
|
|
1294
229
|
|
|
1295
|
-
|
|
230
|
+
| Version | Stable | [Spec] Compatibility | Install |
|
|
231
|
+
| ------- | ------ | -------------------- | ------------------ |
|
|
232
|
+
| 10.x.x | Release Candidate | 2, 3 | See [installation] |
|
|
233
|
+
| 9.x.x | Yes | 2, 3\* | [9xx] |
|
|
1296
234
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
You'll need to add the additional header `Access-Control-Expose-Headers`, this will allow specified headers to be passed to the response object. This can be done by declaring the header in the `willRespondWith` section of your interaction:
|
|
1300
|
-
|
|
1301
|
-
```js
|
|
1302
|
-
"willRespondWith": {
|
|
1303
|
-
"headers": {
|
|
1304
|
-
"Access-Control-Expose-Headers": like("My-Header"),
|
|
1305
|
-
"My-Header": "..."
|
|
1306
|
-
},
|
|
1307
|
-
...
|
|
1308
|
-
}
|
|
1309
|
-
```
|
|
235
|
+
_\*_ v3 support is limited to the subset of functionality required to enable language inter-operable [Message support].
|
|
1310
236
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
### Debugging
|
|
1314
|
-
|
|
1315
|
-
If your standard tricks don't get you anywhere, setting the logLevel to `DEBUG` and increasing the timeout doesn't help and you don't know where else to look, it could be that the binaries we use to do much of the Pact magic aren't starting as expected.
|
|
1316
|
-
|
|
1317
|
-
Try starting the mock service manually and seeing if it comes up. When submitting a bug report, it would be worth running these commands before hand as it will greatly help us:
|
|
1318
|
-
|
|
1319
|
-
```
|
|
1320
|
-
./node_modules/.bin/pact-mock-service
|
|
1321
|
-
```
|
|
237
|
+
</details>
|
|
1322
238
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
```
|
|
1326
|
-
./node_modules/.bin/pact-provider-verifier
|
|
1327
|
-
```
|
|
239
|
+
## Roadmap
|
|
1328
240
|
|
|
241
|
+
The [roadmap](https://docs.pact.io/roadmap/) for Pact and Pact JS is outlined on our main website.
|
|
1329
242
|
## Contributing
|
|
1330
243
|
|
|
1331
|
-
|
|
1332
|
-
2. Create your feature branch from the relevant tree (e.g. [v5] or [v6]) (`git checkout -b my-new-feature`)
|
|
1333
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
1334
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
|
1335
|
-
5. Create new Pull Request
|
|
1336
|
-
|
|
1337
|
-
If you would like to implement `Pact` in another language, please check out the Pact [Spec] and have a chat to one of us on the [pact-dev Google group](https://groups.google.com/forum/#!forum/pact-support).
|
|
1338
|
-
|
|
1339
|
-
The vision is to have a compatible `Pact` implementation in all the commonly used languages, your help would be greatly appreciated!
|
|
1340
|
-
|
|
1341
|
-
## Contact
|
|
244
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
|
1342
245
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
or chat to us at
|
|
1348
|
-
|
|
1349
|
-
- Twitter: [@pact_up](https://twitter.com/pact_up)
|
|
1350
|
-
- Stack Overflow: https://stackoverflow.com/questions/tagged/pact
|
|
1351
|
-
- Google users group: https://groups.google.com/forum/#!forum/pact-support
|
|
246
|
+
<a href="https://github.com/pact-foundation/pact-js/graphs/contributors">
|
|
247
|
+
<img src="https://contrib.rocks/image?repo=pact-foundation/pact-js" />
|
|
248
|
+
</a>
|
|
249
|
+
<br />
|
|
1352
250
|
|
|
1353
|
-
[getting started with pact]: https://docs.pact.io/getting_started
|
|
1354
251
|
[spec]: https://github.com/pact-foundation/pact-specification
|
|
252
|
+
[9xx]: https://github.com/pact-foundation/pact-js/
|
|
253
|
+
[pact website]: http://docs.pact.io/
|
|
254
|
+
[@pact_up]: https://twitter.com/pact_up
|
|
255
|
+
[pact specification v2]: https://github.com/pact-foundation/pact-specification/tree/version-2
|
|
256
|
+
[pact specification v3]: https://github.com/pact-foundation/pact-specification/tree/version-3
|
|
257
|
+
[library]: https://github.com/pact-foundation/pact-reference/releases
|
|
258
|
+
[installation]: #installation
|
|
259
|
+
[message support]: https://github.com/pact-foundation/pact-specification/tree/version-3#introduces-messages-for-services-that-communicate-via-event-streams-and-message-queues
|
|
1355
260
|
[changelog]: https://github.com/pact-foundation/pact-js/blob/master/CHANGELOG.md
|
|
1356
|
-
[
|
|
261
|
+
[pact broker]: https://github.com/pact-foundation/pact_broker
|
|
262
|
+
[pactflow]: https://pactflow.io
|