Jest-Pact – A Jest-adaptor to help write Pact files with ease

In previous posts, I have spoken about Pact.io. A wonderful set of tools, designed to help you and your team develop smarter, with consumer-driven contract tests.

We use Jest at work to test our TypeScript code, so it made sense to use Jest as our testing framework, to write our Pact unit tests with.

The Jest example on Pact-JS, involve a-lot of setup, which resulted in a fair bit of cognitive-load before a developer could start writing their Contract tests.

Inspired by a post by Tim Jones, one of the maintainers of Pact-JS and a member of the Dius team who built PACT, I decided to build and release an adapter for Jest, which would abstract the pact setup away from the developer, leaving them to concentrate on the tests.

Features

  •  instantiates the PactOptions for you
  •  Setups Pact mock service before and after hooks so you don’t have to
  •  Assign random ports and pass port back to user so we can run in parallel without port clashes

Adapter Installation

npm i jest-pact --save-dev

OR

yarn add jest-pact --dev

Usage

pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, provider => {
    // regular pact tests go here
}

Example

Say that your API layer looks something like this:

import axios from 'axios';

const defaultBaseUrl = "http://your-api.example.com"

export const api = (baseUrl = defaultBaseUrl) => ({
     getHealth: () => axios.get(`${baseUrl}/health`)
                    .then(response => response.data.status)
    /* other endpoints here */
})

Then your test might look like:

import { pactWith } from 'jest-pact';
import { Matchers } from '@pact-foundation/pact';
import api from 'yourCode';

pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, provider => {
  let client;
  
  beforeEach(() => {
    client = api(provider.mockService.baseUrl)
  });

  describe('health endpoint', () => {
    // Here we set up the interaction that the Pact
    // mock provider will expect.
    //
    // jest-pact takes care of validating and tearing 
    // down the provider for you. 
    beforeEach(() =>
      provider.addInteraction({
        state: "Server is healthy",
        uponReceiving: 'A request for API health',
        willRespondWith: {
          status: 200,
          body: {
            status: Matchers.like('up'),
          },
        },
        withRequest: {
          method: 'GET',
          path: '/health',
        },
      })
    );
    
    // You also test that the API returns the correct 
    // response to the data layer. 
    //
    // Although Pact will ensure that the provider
    // returned the expected object, you need to test that
    // your code recieves the right object.
    //
    // This is often the same as the object that was 
    // in the network response, but (as illustrated 
    // here) not always.
    it('returns server health', () =>
      client.health().then(health => {
        expect(health).toEqual('up');
      }));
  });

You can make your tests easier to read by extracting your request and responses:

/* pact.fixtures.js */
import { Matchers } from '@pact-foundation/pact';

export const healthRequest = {
  uponReceiving: 'A request for API health',
  withRequest: {
    method: 'GET',
    path: '/health',
  },
};

export const healthyResponse = {
  status: 200,
  body: {
    status: Matchers.like('up'),
  },
} 
import { pactWith } from 'jest-pact';
import { healthRequest, healthyResponse } from "./pact.fixtures";

import api from 'yourCode';

pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, provider => {
  let client;
  
  beforeEach(() => {
    client = api(provider.mockService.baseUrl)
  });

  describe('health endpoint', () => {

    beforeEach(() =>
      provider.addInteraction({
        state: "Server is healthy",
        ...healthRequest,
        willRespondWith: healthyResponse
      })
    );
    
    it('returns server health', () =>
      client.health().then(health => {
        expect(health).toEqual('up');
      }));
  });

Configuration

pactWith(PactOptions, provider => {
    // regular pact tests go here
}

interface PactOptions {
  provider: string;
  consumer: string;
  port?: number; // defaults to a random port if not provided
  pactfileWriteMode?: PactFileWriteMode;
  dir? string // defaults to pact/pacts if not provided
}

type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
type PactFileWriteMode = "overwrite" | "update" | "merge";

Defaults

  • Log files are written to /pact/logs
  • Pact files are written to /pact/pacts

Jest Watch Mode

By default Jest will watch all your files for changes, which means it will run in an infinite loop as your pact tests will generate json pact files and log files.

You can get round this by using the following watchPathIgnorePatterns: ["pact/logs/*","pact/pacts/*"] in your jest.config.js

Example

module.exports = {
  testMatch: ["**/*.test.(ts|js)", "**/*.it.(ts|js)", "**/*.pacttest.(ts|js)"],
  watchPathIgnorePatterns: ["pact/logs/*", "pact/pacts/*"]
};

You can now run your tests with jest --watch and when you change a pact file, or your source code, your pact tests will run

Examples of usage of jest-pact

See Jest-Pact-Typescript which showcases a full consumer workflow written in Typescript with Jest, using this adaptor

  •  Example pact tests
    •  AWS v4 Signed API Gateway Provider
    •  Soap API provider
    •  File upload API provider
    •  JSON API provider

Examples Installation

  • clone repository git@github.com:YOU54F/jest-pact-typescript.git
  • Run yarn install
  • Run yarn run pact-test

Generated pacts will be output in pact/pacts Log files will be output in pact/logs

Credits

Published by

YOU54F

I have been a Software Test Engineer for 11+ years now, starting off in Accessibility Testing at a large UK Banking organisation. I have since worked with financial / medical healthcare / betting / telecommunication providers testing software and along the way helping some migrate from traditional software development methodologies to a leaner Agile based approach. I now work for a consultancy company, providing insight into tools / technologies / approaches to ensure we are testing the right things, at the right place in the stack. Less of my time these days is spent writing test code, and more time championing test processes with our developers, who have picked it up and hit the ground running.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.