Contract testing with PactJS and Jest

Jun Wei Ng
8 min readOct 28, 2019

So you wanted to start writing contract testing using PactJS, but isn’t too comfortable using mocha as used in many of the examples on the Internet. Fret not, because PactJS can be used with Jest as well, with some minor portholes to look out for, as always.

First off, let’s do a little recap on the basics of contract testing before we dive deeper into using PactJS with Jest.

What is contract testing

I am sure you have read the docs on pact.io before, and they have given a pretty in-depth explanation on what Contract Testing is. From their docs:

Contract testing ensures that a pair of applications will work correctly together by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a “contract”.

Another way to put it is that the consumer and the provider are tested individually against a contract made on mutually agreed upon understanding of how data should flow between them.

Contract testing vs. functional testing

Also from the docs on pact.io, it is mentioned that Contract testing is not Functional testing. From their docs, once again:

Contract tests focus on the messages that flow between a consumer and provider, while functional tests also ensure that the correct side effects have occured.

To put it simply, a contract test only tests for the data that flows between the consumer and the provider, given certain assumptions of the state of the provider. Functional tests are concerned with the side effects of the tests in addition to the data that flows between the consumer and provider. Some of these side effects can be:

  • Calls have been made to other services (both internal and external),
  • Databases have been used,
  • Etc. etc.

Consumer driven design

By using contract testing, the providers are giving the consumer a chance to shape the design of the provider service. This is important as the consumer is the one that actually drives the demand on the usage of the provider service. It only makes sense for the consumer to drive the design so that the provide service can evolve accordingly to the needs of the consumer. Martin Fowler has a blog post on Consumer-Driven Contracts that you can read for more inspiration.

What is a Pact

A pact is a contract made between the consumer and the provider. The pact should encompass all the agreed upon endpoints and how they will be used i.e. how data should be sent in the request, how data should be like in the response, and how it differs according to the state of the provider.

The brief outline of a PactJs contract looks as such:

Several things to note if one is to follow the above template:

Each nested describe block should contain only ONE it or test block.

This is because the “global” afterEach() performs a provider.verify(), which clears any interaction in the provider mock server. To get around this, one can either:

  1. Move provider.verify() into each nested describe block’s afterAll()block, or
  2. Change the nested describe block’s beforeAll() to beforeEach() so that interactions are added before each nested test block.

Pact provider mock service

We will need to create a mock service to represent the provider in our contract test. For example:

The mock provider service is configured to:

  • Use a specified port for hosting its endpoints,
  • Logs usage (of the PactJS library) to a specified location,
  • Generate the Pact to a specified location,
  • Use a specified name for both the consumer and provider respectively, and
  • Log usage according to a specified log level.

Pact interactions

The interactions between the consumer and the provider define the pact between them. An interaction can be thought of as how the provider respond when a request from a consumer is received.

In a Pact interaction, one can define:

  • the state of the provider (optional),
  • the shape of the request, and
  • the shape of the expected response.

An example of a POST request interaction to create a new user is as such:

An example of a GET request interaction to retrieve the list of users with pagination in the query is as such:

Using Matchers

In the above examples, the fields are matched using strict equality. That results in a contract that is highly restrictive for the provider. A highly restrictive contract makes it difficult for a provider to evolve and might even restrict changes to its business logic. That is highly undesired, as the provider shouldn’t be constrained by its consumers.

This is where Pact Matchers can help. Pact Matchers allow us to define how fields in the request and response should be matched, either using strict equality, or using regular expression, or even using just type check.

Pact supports several types of matchers, but most of them are internally calling two of the matchers — somethingLike and term.

Pact.Matcher.somethingLike

The somethingLike matcher is used to match that the field is of the same type
as the specified value. The somethingLike matcher is also aliased as like in the PactJS library for convenience.

Pact.Matcher.term

The term matcher is used to perform a match using regular expression. The term matcher is also aliased as regex in the PactJS library for convenience.

The parameter to term() is an object with two required fields:

  • matcher - a regular expression to match against the provided value
  • generate - a value used in the generated pact to represent the matched string

Creating the Pact (as the consumer)

Once you have your pact interactions ready and verified, it is time to create the pact that the provider will use to verify against. The gist to creating the pact is:

This creates the pact JSON file in the previous specified location when defining the Pact provider mock server. In our example above, the pact JSON file will be created in the folder named pacts in the current working directory of where the command to run the contract tests was executed.

The pact should then be shared with the provider. This can be done in several ways — a shared folder, email, exposed through some endpoint — but the best way is to do it via a Pact Broker.

Verifying the pact (at the provider)

At the provider’s end, the verification is done via the Pact.Verifier object. In the following example, the pact JSON file is assumed to be found in a folder named pacts in the current working directory (not optimal).

Using a Pact Broker

The best way for the consumer and the provider to share a pact is through a Pact Broker. The general idea is for a third-party to hold the generated pact between the consumer and the provider, and track the latest version of the consumer and provider that fulfils a certain version of the pact.

Setting up a Pact Broker has been made fairly easy by the Dockerised Pact Broker by DiUS and pact-foundation. You can find the pact-foundation/pact-broker-docker here.

More information can be found at:

Hosting your own Pact Broker

To start off, either clone the entire git repository of pact-foundation/pact-broker-docker, or just copy the necessary portions you need. For our case, we will need the docker-compose.yml file only.

The steps to hosting your own Pact Broker using Docker Compose are:

  1. Modify the docker-compose.yml as needed.
  2. Spin up the containers using docker-compose up.
  3. Done. Easy as that.

Viewing the pacts on the broker

You can access the Pact Broker through `http://localhost:PORT`, where the default port number is 9292.

Uploading a pact to the broker

You can upload a pact to the broker using the following command. Note that the pact is a JSON file.

You can specify the provider and consumer name, and the version of the pact. In the above example, the provider is named Provider Name and consumer is named Consumer Name, and the pact version is 1.0.0.

Note that the name of the provider and the consumer HAS TO MATCH the name in the JSON file. Failure to do so will result in an HTTP 400 Bad Request response with an error message indicating that the names does not match.

Publishing a verification result to the Pact Broker

To publish the verification result to the broker, more options have to be set in the provider verifier:

Note that publishVerificationResult and providerVersion has to be either BOTH present, or BOTH not present.

In summary

Pact is a useful tool to allow consumers and providers to ensure that interactions are agreed upon by all involved parties. This helps ensure that there are no surprises when performing integration between the services and makes delivery smoother. The key is (as always) communication between the developers.

The following section goes into some of the gotchas I faced while working with PactJS and how I got around them.

Happy coding! (:

Gotchas

When I started using PactJS, there are several incidents that I spent hours reading through documentations and forums before figuring out the cause and the solution. The cause is often very simple, and the solution even simpler, and I wished that someone has written about it in a more publicly available medium.

And so, here I have compiled the list of gotchas that might be of help to you when starting off:

Adding a second interaction only work sometimes!

Now, upon seeing the word ‘sometimes’, you can guess that it might be something to do with concurrency. On my first attempt to add a second interaction that heads down the unhappy-path, I was faced with one of the following scenarios after running the tests:

  1. Got a ERROR -- : No matching interaction found.. in the log file, or
  2. Got a ERROR -- : Multiple interactions found.. in the log file, or
  3. Things just worked out as expected!

The reason for the differing results in different run is due to race condition (I knew it!).

The expected sequence is as follow:

However, if a test added an interaction before the previous test has
verified its interaction, the No matching interactions error will be raised.
This is because verify() performs a removeInteractions() as well, which clears up all interactions in the provider mock server.

If a test added an interaction that has the same path as the interaction added in a previous test, and the test executes its test before the previous test has verified its interaction, the Multiple matching interactions error will be raised. This is because PactJS does not know which interactions to use as there are multiple interactions with the same path.

Solution

Jest has to be run using the --runInBand flag to ensure that the tests run in the expected sequence.

I ran Jest “in band” as mentioned above and yet I still face some inconsistent results!

Even after running Jest using the runInBand flag, you can still face inconsistent results where a second interaction might get a No matching interaction or Multiple matching interaction error. This might be due to the way the previous interaction is verified by the provider mock server.

Note that provider.verify() returns a promise, and if not “await-ed” properly, there will be issues with race conditions. To not get into race conditions, one can either:

  • Use return provider.verify();, or
  • Use proper Promise resolving techniques such as async/await as shown in the template above.

I’m getting a “No content-type found..” error but my pact was generated successfully?!

This is caused when the request in the interaction did not specify a
Content-Type in the headers, and the request has a body.

Solution

Specify the content type in the request headers.

A simple exact matching example:

--

--

Jun Wei Ng

Software developer @ Thoughtworks. Opinions are my own