Skip to main content

Provider Verification

Pact Go supports both HTTP and non-HTTP verification (using plugins).

Contract Testing Process (HTTP)

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.

The process looks like this:

diagram

  1. The consumer writes a unit test of its behaviour using a Mock provided by Pact
  2. Pact writes the interactions into a contract file (as a JSON document)
  3. The consumer publishes the contract to a broker (or shares the file in some other way)
  4. Pact retrieves the contracts and replays the requests against a locally running provider
  5. The provider should stub out its dependencies during a Pact test, to ensure tests are fast and more deterministic.

In this document, we will cover steps 4-5.

HTTP Provider package

The provider interface is in the package: github.com/pact-foundation/pact-go/v2/provider.

Provider API Testing

Provider API

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:

APIOptionsReturnsDescription
verifyProvider()See belowPromiseStart the Mock Server
  1. Start your local Provider service.
  2. Optionally, instrument your API with ability to configure provider states
  3. Then run the Provider side verification step
const { Verifier } = require('@pact-foundation/pact');
const opts = {
...
};

new Verifier(opts).verifyProvider().then(function () {
// do something
});

Verification Options

Verification Options
ParameterRequired?TypeDescription
providerBaseUrltruestringRunning API provider host endpoint.
pactBrokerUrlfalsestringBase URL of the Pact Broker from which to retrieve the pacts. Required if pactUrls not given.
providerfalsestringName of the provider if fetching from a Broker
consumerVersionSelectorsfalseConsumerVersionSelector|arrayUsing Selectors is a way we specify which pacticipants and versions we want to use when configuring verifications.
consumerVersionTagsfalsestring|arrayRetrieve the latest pacts with given tag(s)
providerVersionTagsfalsestring|arrayTag(s) to apply to the provider application
providerVersionBranchfalsestringBranch to apply to provider application version (recommended to set)
includeWipPactsSincefalsestringIncludes pact marked as WIP since this date. String in the format %Y-%m-%d or %Y-%m-%dT%H:%M:%S.000%:z
pactUrlsfalsearrayArray of local pact file paths or HTTP-based URLs. Required if not using a Pact Broker.
providerStatesSetupUrlfalsestringDeprecated (use URL to send PUT requests to setup a given provider state
stateHandlersfalseobjectMap of "state" to a function that sets up a given provider state. See docs below for more information
requestFilterfalsefunction (Express middleware)Function that may be used to alter the incoming request or outgoing response from the verification process. See below for use.
beforeEachfalsefunctionFunction to execute prior to each interaction being validated
afterEachfalsefunctionFunction to execute after each interaction has been validated
pactBrokerUsernamefalsestringUsername for Pact Broker basic authentication
pactBrokerPasswordfalsestringPassword for Pact Broker basic authentication
pactBrokerTokenfalsestringBearer token for Pact Broker authentication
publishVerificationResultfalsebooleanPublish verification result to Broker (NOTE: you should only enable this during CI builds)
providerVersionfalsestringProvider version, required to publish verification result to Broker. Optional otherwise.
enablePendingfalsebooleanEnable the pending pacts feature.
timeoutfalsenumberThe duration in ms we should wait to confirm verification process was successful. Defaults to 30000.
logLevelfalsestringnot used, log level is set by environment variable

To dynamically retrieve pacts from a Pact Broker for a provider, provide the broker URL, the name of the provider, and the consumer version tags that you want to verify:

const opts = {
pactBroker: 'http://my-broker',
provider: 'Animal Profile Service',
consumerVersionTags: ['master', 'test', 'prod'],
};

To verify a pact at a specific URL (eg. when running a pact verification triggered by a 'contract content changed' webhook, or when verifying a pact from your local machine, or a network location that's not the Pact Broker, set just the pactUrls, eg:

const opts = {
pactUrls: [process.env.PACT_URL],
};

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:

const opts = {
publishVerificationResult: true, //generally you'd do something like `process.env.CI === 'true'`
providerVersion: 'version', //recommended to be the git sha
providerVersionTags: ['tag'], //optional, recommended to be the git branch
};

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.

Read more about Verifying Pacts.

API with Provider States

If you have defined any states 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:

const opts = {
...
stateHandlers: {
[null]: () => {
// This is the "default" state handler, when no state is given
}
"Has no animals": () => {
animalRepository.clear()
return Promise.resolve(`Animals removed from the db`)
},
"Has some animals": () => {
importData()
return Promise.resolve(`Animals added to the db`)
},
"Has an animal with ID 1": () => {
importData()
return Promise.resolve(`Animals added to the db`)
}
}
}

return new Verifier(opts).verifyProvider().then(...)

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.

Read more about Provider States.

Provider State Setup and Teardown

Provider States can optionally take a setup and teardown function. These are useful in situations where you'd like to cleanup data specific to the provider state.

  'Whatever your state name is': {
setup: (parameters) => {
// do your setup here
// return a promise if you need to
},
teardown: (parameters) => {
// do your teardown here
// return a promise if you need to
},
},

You can think of regular provider states as only being the "setup" phase.

Before and After Hooks

Sometimes, it's useful to be able to do things before or after a test has run, such as reset a database, log a metric etc. A beforeEach hook runs on each verification before any other part of the Pact test lifecycle, and a afterEach hook runs as the last step before returning the verification result back to the test.

You can add them to your verification options as follows:

const opts = {
...
beforeEach: () => {
console.log('I run before everything else')
},

afterEach: () => {
console.log('I run after everything else has finished')
}
}

If the hook errors, the test will fail. See the lifecycle of an interaction below.

Pending Pacts

NOTE: This feature is available on [PactFlow] by default, and requires configuration if using a self-hosted broker.

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.

This enables safe introduction of new contracts into the system, without breaking Provider builds, whilst still providing feedback to Consumers as per before.

See the docs and this article for more background.

WIP Pacts

NOTE: This feature is available on [PactFlow] by default, and requires configuration if using a self-hosted broker.

WIP Pacts builds upon pending pacts, enabling provider tests to pull in any contracts applicable to the provider regardless of the tag it was given. This is useful, because often times consumers won't follow the exact same tagging convention and so their workflow would be interrupted. This feature enables any pacts determined to be "work in progress" to be verified by the Provider, without causing a build failure. You can enable this behaviour by specifying a valid timestamp for includeWipPactsSince. This sets the start window for which new WIP pacts will be pulled down for verification, regardless of the tag.

See the docs and this article for more background.

Verifying multiple contracts with the same tag (e.g. for Mobile use cases)

Tags may be used to indicate a particular version of an application has been deployed to an environment - e.g. prod, and are critical in configuring can-i-deploy checks for CI/CD pipelines. In the majority of cases, only one version of an application is deployed to an environment at a time. For example, an API and a Website are usually deployed in replacement of an existing system, and any transition period is quite short lived.

Mobile is an exception to this rule - it is common to have multiple versions of an application that are in "production" simultaneously. To support this workflow, we have a feature known as consumer version selectors. Using selectors, we can verify that all pacts with a given tag should be verified. The following selectors ask the broker to "find all pacts with tag 'prod' and the latest pact for 'master'":

consumerVersionSelectors: [
{
tag: 'prod',
all: true,
},
{
tag: 'master',
latest: true,
},
];

NOTE: Using the all flag requires you to ensure you delete any tags associated with application versions that are no longer in production (e.g. if decommissioned from the app store)

Modify Requests Prior to Verification (Request Filters)

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.

For these cases, we the ability to modify a request/response and modify the payload. The flag to achieve this is requestFilter.

Example API with Authorization

For example, to have an Authorization bearer token header sent as part of the verification request, set the verifyProvider options as per below:

let token
const opts = {
provider: 'Animal Profile Service',
...
stateHandlers: {
"is authenticated": () => {
token = "1234"
Promise.resolve(`Valid bearer token generated`)
},
"is not authenticated": () => {
token = ""
Promise.resolve(`Expired bearer token generated`)
}
},

// this middleware is executed for each request, allowing `token` to change between invocations
// it is common to pair this with `stateHandlers` as per above, that can set/expire the token
// for different test cases
requestFilter: (req, res, next) => {
req.headers["Authorization"] = `Bearer: ${token}`
next()
},
}

return new Verifier(opts).verifyProvider().then(...)

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.

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!

Lifecycle of a provider verification

For each interaction in a pact file, the order of execution is as follows:

BeforeEach -> State Handler -> Request Filter (request phase) -> Execute Provider Test -> Request Filter (response phase) -> AfterEach

If any of the middleware or hooks fail, the tests will also fail.

Publishing Pacts to a Broker

Sharing is caring - to simplify sharing Pacts between Consumers and Providers, we have created the Pact Broker.

The Broker:

  • versions your contracts
  • tells you which versions of your applications can be deployed safely together
  • allows you to deploy your services independently
  • provides API documentation of your applications that is guaranteed to be up-to date
  • visualises the relationships between your services
  • integrates with other systems, such as Slack or your CI server, via webhooks
  • ...and much much more.

Host your own using the open source docker image, or sign-up for a free hosted Pact Broker with our friends at PactFlow.

Publish in npm scripts

The easiest way to publish pacts to the broker is via an npm script in your package.json:


"test:publish": "./node_modules/.bin/pact-broker publish <YOUR_PACT_FILES_OR_DIR> --consumer-app-version=\"$(npx absolute-version)\" --auto-detect-version-properties --broker-base-url=https://your-broker-url.example.com"

For a full list of the options, see the CLI usage instructions. All CLI binaries are available in npm scripts when using pact-js.

If you want to pass your username and password to the broker without including them in scripts, you can provide it via the environment variables PACT_BROKER_USERNAME and PACT_BROKER_PASSWORD. If your broker supports an access token instead of a password, use the environment variable PACT_BROKER_TOKEN.

Publishing Verification Results to a Pact Broker

If you're using a Pact Broker, (e.g. a hosted one with our friends at PactFlow), you can publish your verification results so that consumers can query if they are safe to release.

It looks like this:

screenshot of verification result

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:

const opts = {
publishVerificationResult: true, //recommended to only publish from CI by setting the value to `process.env.CI === 'true'`
providerVersion: 'version', //recommended to be the git sha eg. process.env.MY_CI_COMMIT
providerVersionBranch: 'master', //recommended to be the git branch eg. process.env.MY_GIT_SHA
providerVersionTags: ['tag'], //optional, recommended to be the git branch eg. process.env.MY_CI_BRANCH
};