DEV Community

Cover image for Consumer Driven Contract Testing with Pact (JavaScript)
Sawjan for JankariTech

Posted on

Consumer Driven Contract Testing with Pact (JavaScript)

Contract testing is a software testing technique which is used to test the integration points and interfaces between different software applications. The main goal of contract testing is to test each application in isolation and ensure that they are compatible with a shared contract.

Consumer driven contract testing is simply a contract testing approach in which a consumer sets the list of expectations as a contract and expects a provider to be compatible with that contract.

Pact

Pact is a code-first tool for testing HTTP and message integrations using contract tests.
For more information see docs.pact.io

In this blog post, I will focus on how to test HTTP integrations using Pact. The following diagram shows the overview of how Pact works:

Pact Workflow

Overview of How Pact Works

For a step-by-step explanation, please refer to How Pact works.

Pact is available in more than 10 programming languages. See here for supported programming languages. In this blog post, I will focus on the JavaScript implementation of Pact.

Writing Tests with PactJs (Consumer Side)

Let's assume that you have a web application that gets earth's total population from an API server and you want to test that application without having to actually communicate with the real server. This is where Pact comes in.

Before writing any tests, you need to have a working web application. Let's create one.

Install axios: required to make HTTP requests

npm i axios@0.21.1
Enter fullscreen mode Exit fullscreen mode

Create a client.js file inside a src folder and write the following code:

// src/client.js
const axios = require('axios');
function Earth(api_server_url) {
  this.AXIOS = axios.create({ baseURL: api_server_url });
  this.getTotalPopulation = function () {
    return this.AXIOS.get('/population').then((res) => res.data);
  };
}
module.exports = Earth;
Enter fullscreen mode Exit fullscreen mode

Now, we are ready to write some tests.

Installation

We will be using PactV3 with Jest in this example.
Install PactV3 and Jest using the following command:

npm i -D @pact-foundation/pact@10.0.0-beta.44 jest@27.0.6
Enter fullscreen mode Exit fullscreen mode

Writing Tests

Create client.spec.js file inside a tests folder. This spec file is our test file.

The test looks like this:

Note: The following example may or may not work with the latest version of above packages

// tests/client.spec.js
const path = require('path');
const Earth = require('../src/client');
const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
describe('Test', () => {
  // pact mock server url
  const mock_port = 1234;
  const mock_server_url = 'http://127.0.0.1:' + mock_port;
  // pact instance
  const provider = new PactV3({
    consumer: 'web_server',
    provider: 'api_server',
    port: mock_port,
    dir: path.resolve(process.cwd(), 'tests', 'pacts'),
  });
  it('test: getTotalPopulation', () => {
    // interaction
    provider
      .uponReceiving("a GET request to get total earth's population")
      .withRequest({
        method: 'GET',
        path: '/population',
      })
      .willRespondWith({
        status: 200,
        headers: {
          'Content-Type': 'application/json',
        },
        body: {
          population: MatchersV3.number(7794798739),
        },
      });
    return provider.executeTest(() => {
      const earth = new Earth(mock_server_url);
      return earth
        .getTotalPopulation()
        .then((res) => {
          expect(res.population).toBe(7794798739);
        })
        .catch((err) => {
          expect(err).toBeNull();
        });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

In the above test, firstly, we created the pact instance with mandatory options except port (if the port is not provided, the mock server will run on a random port). Then, we added the interaction that we want to test.

During the test run, the test function getTotalPopulation will send the expected request to the mock server.

.withRequest({
    method: 'GET',
    path: '/population',
})
Enter fullscreen mode Exit fullscreen mode

And the mock server will respond with the expected response.

.willRespondWith({
    status: 200,
    headers: {
        'Content-Type': 'application/json',
    },
    body: {
        population: MatchersV3.number(7794798739),
    },
})
Enter fullscreen mode Exit fullscreen mode

MatchersV3 provides a set of matchers that can be used to check the response. For detailed information please read Using the V3 matching rules

It is important to note that the test function call and assertions should be done within the callback block of executeTest. Function executeTest is responsible for starting and, stopping the mock server and also for writing the pact file.

Now, as you have your first test, you can run the test using the following command:

npx jest tests/client.spec.js
Enter fullscreen mode Exit fullscreen mode

Result:
Consumer test result

When a test run exits with success, it will generate a json file (i.e. pact or contract file) inside the pacts folder which is later used to verify the provider.

Verifying the Provider (Provider Side)

You have written tests for your web application. But now, you also need to verify that your API server returns the expected response as per the contract. As I have mentioned above, you need a pact file (contract) in order to verify the provider (API server).

Let's create a simple API server using express which will only respond to the /population endpoint.

Install express with the following command:

npm i express@4.17.1
Enter fullscreen mode Exit fullscreen mode

Create a server.js file inside a src folder and write the following code:

// src/server.js
const express = require('express');
const app = express();
const port = 5000;
app.get('/population', (req, res) => {
  res.append('Content-Type', 'application/json').status(200).send({
    populatioin: 123456789,
  });
});
app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
Enter fullscreen mode Exit fullscreen mode

To run the server, run the following command:

node src/server
Enter fullscreen mode Exit fullscreen mode

Now the API server is up and running, let's write a test file to verify the contract against the provider.

// tests/server.spec.js
const { VerifierV3 } = require('@pact-foundation/pact');
const path = require('path');
const assert = require('assert');
describe('Pact Verification', () => {
  it('verifies the provider', () => {
    const options = {
      provider: 'api_server',
      providerBaseUrl: 'http://localhost:5000',
      disableSSLVerification: true,
      pactUrls: [
        path.resolve(
          process.cwd(),
          'tests',
          'pacts',
          'web_server-api_server.json'
        ),
      ],
    };
    return new VerifierV3(options)
      .verifyProvider()
      .then((output) => {
        console.log('Pact Verification Complete!');
        console.log('Result:', output);
      })
      .catch(function (error) {
        console.log(error);
        assert.fail();
      });
  });
});
Enter fullscreen mode Exit fullscreen mode

The important things to note in the above test are:

  • providerBaseUrl: This is the URL of the API server.

  • pactUrls: This is the path to the pact file. The pact file is generated by the consumer side tests.

Run the provider verification test using the following command:

npx jest tests/server.spec.js
Enter fullscreen mode Exit fullscreen mode

Result:
Provider test result

Congratulations! You have successfully written your first consumer driven contract test and provider verification test using Pact.

Discussion (0)