Implementing a Consumer-Driven Contract for a Node Express Server with Pact

Table Of Contents

Consumer-driven contract (CDC) tests are a technique to test integration points between API providers and API consumers without the hassle of end-to-end tests (read it up in a recent blog post). A common use case for consumer-driven contract tests is testing interfaces between services in a microservice architecture.

In this tutorial, we’re going to create a REST provider with Node and Express that implements the Heroes endpoints from the contract created in this article.

Then, we’ll create a contract test with the JavaScript version of Pact that verifies that our provider works as specified in the contract.

This tutorial assumes you have a current version of Node installed.

Example Code

This article is accompanied by a working code example on GitHub.

Creating an Express Server

Let’s start by creating an Express server from scratch.

Since we don’t want to do this by hand, we’ll install the express-generator:

npm install -g express-generator 

Then, we simply call the generator to create a project template for us:

express --no-view pact-node-provider

We’re using the --no-view parameter since we’re only implementing REST endpoint and thus don’t need any templating engine.

Don’t forget to call npm install in the created project folder now to install the dependencies.

Adding the Heroes Endpoint

Having a base Express project, we’re ready to implement a new REST endpoint.

The Contract

But first, let’s have a look at the contract against which we’re about to implement. The contract has been created by the consumer in this article:

{
  "consumer": {
    "name": "hero-consumer"
  },
  "provider": {
    "name": "hero-provider"
  },
  "interactions": [
    {
      "description": "a POST request to create a hero",
      "providerState": "provider allows hero creation",
      "request": {
        "method": "POST",
        "path": "/heroes",
        "headers": {
          "Accept": "application/json; charset=utf-8",
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": {
          "name": "Superman",
          "superpower": "flying",
          "universe": "DC"
        },
        "matchingRules": {
          "$.headers.Accept": {
            "match": "regex",
            "regex": "application\\/json; *charset=utf-8"
          },
          "$.headers.Content-Type": {
            "match": "regex",
            "regex": "application\\/json; *charset=utf-8"
          }
        }
      },
      "response": {
        "status": 201,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": {
          "id": 42,
          "name": "Superman",
          "superpower": "flying",
          "universe": "DC"
        },
        "matchingRules": {
          "$.headers.Content-Type": {
            "match": "regex",
            "regex": "application\\/json; *charset=utf-8"
          },
          "$.body": {
            "match": "type"
          }
        }
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "2.0.0"
    }
  }
}

The contract contains a single request / response pair, called an “interaction”. In this interaction, the consumer sends a POST request with a Hero-JSON-object in the body and the provider is expected to return a response with HTTP status 201 which again contains the hero in the body, this time with an ID that was added by the server.

In the following, we assume that the contract has been published on a Pact Broker by the consumer. But it’s also possible to take the contract file from the consumer codebase and access it directly in the provider code base.

Adding an Express Route

We now want to implement the contract on the provider side.

For this, we create a new POST route that expects a hero JSON object as payload:

// ./routes/heroes.js
const express = require('express');
const router = express.Router();

router.route('/')
    .post(function (req, res) {
        res.status(201);
        res.json({
            id: 42,
            superpower: 'flying',
            name: 'Superman',
            universe: 'DC'
        });
    });

module.exports = router;

I highly recommend to add some kind of validation to check the incoming request (e.g. check that the body contains all expected fields). I explained here why validation immensely improves the quality of our contract tests.

Now we have to make the new route available to the Express application by adding it in app.js:

// ./app.js
const heroesRouter = require('./routes/heroes');
app.use('/heroes', heroesRouter);

We just implemented the provider side of the contract. We can check if it works by calling npm run start and sending a POST request to http://localhost:3000/heroes with a REST client tool. Or, we can just type the URL into your browser. However, we’ll get HTTP status 405 then, because the browser sends a GET request and a POST request is expected.

Now we have to prove that the provider actually works as expected by the contract.

Setting Up Pact

So, let’s set up Pact to implement a provider test that verifies our endpoint against the contract.

The provider test reads the interactions from a contract and for each interaction, does the following:

  1. put the provider into a state that allows to respond accordingly
  2. send the request to the provider
  3. validate that the response from the provider matches the response from the contract.

Pact does most of the work here, we just need to set it up correctly.

Dependencies

First, we add some dependencies to package.json:

// ./package.json
{
  "devDependencies": {
    "@pact-foundation/pact": "7.0.3",
    "start-server-and-test": "^1.7.5"
  }
}
  • we use pact to interpret a given contract file and create a provider test for us
  • we use start-server-and-test to allow us to start up the Express server and the provider test at once.

Adding a Provider State Endpoint

The first step of the provider test for each interaction is to put the provider into a certain state, called “provider state” in Pact lingo.

In the contract above the provider state for our single interaction is called “provider allows hero creation”.

Provider states can be used by the provider to mock database queries, for example. When the provider is notified to go into the state “provider allows hero creation” it knows which database queries are needed and can set up mocks that simulate the database accordingly.

Thus, we don’t need to spin up a database during the test. A major advantage of CDC tests is to be able to execute them without spinning up a whole server farm with a database and other dependencies. Hence, we should make use of mocks that react to the provider states.

You can read more about provider states in the Pact docs.

In order to put the provider into a certain state, it needs a POST endpoint that accepts the consumer and state query parameters:

// ./routes/provider_state.js
const express = require('express');
const router = express.Router();

router.route('/')
    .post(function (req, res) {
        const consumer = req.query['consumer'];
        const providerState = req.query['state'];
        // imagine we're setting the server into a certain state
        res.send(`changed to provider state "${providerState}" for consumer "${consumer}"`);
        res.status(200);
    });

module.exports = router;

Note that the endpoint implementation above is just a dummy implementation. We don’t have any database access in our /heroes endpoint, hence we don’t need to mock anything.

Next, we make the endpoint available to the Express app:

// ./app.js
var providerStateRouter = require('./routes/provider_state');

if (process.env.PACT_MODE === 'true') {
    app.use('/provider-state', providerStateRouter);
}

We only activate the endpoint when the environment variable PACT_MODE is set to true, since we don’t want this endpoint in production.

Make sure to set this environment variable when running the test later.

Providing an endpoint that is only needed in tests is quite invasive. There’s a feature proposal that provides “state handlers” that can react to provider states within your provider test. This way, we can mock external dependencies depending on the provider state more cleanly within the test, instead of “polluting” our application with a dedicated endpoint. However, this feature has not made it into Pact, yet.

Creating a Provider-Side Contract Test

Now we create a script pact/provider_tests.js to use Pact to do the actual testing:

// ./pact/provider_tests.js
const { Verifier } = require('@pact-foundation/pact');
const packageJson = require('../package.json');

let opts = {
    providerBaseUrl: 'http://localhost:3000',
    pactBrokerUrl: 'https://adesso.pact.dius.com.au',
    pactBrokerUsername: process.env.PACT_USERNAME,
    pactBrokerPassword: process.env.PACT_PASSWORD,
    provider: 'hero-provider',
    publishVerificationResult: true,
    providerVersion: packageJson.version,
    providerStatesSetupUrl: 'http://localhost:3000/provider-state'
};

new Verifier().verifyProvider(opts).then(function () {
    console.log("Pacts successfully verified!");
});

In the script we define some options and pass them to a Verifier instance that executes the three steps (provider state, send request, validate response).

The most important options are:

  • pactBroker…: coordinates to the pact broker instance where Pact can download the contracts. Username and password are read from environment variables since we don’t want to include them in code.
  • provider: we tell pact to download only contracts for the provider we’re currently implementing, which in this case is hero-provider.
  • providerBaseUrl: base url of the provider to which the requests are going to be sent. In our case, we’re starting the Express server locally on port 3000.
  • providerStatesSetupUrl: the url to change provider states. This refers to the endpoint we have created above. In our case, we could actually leave this option out, since our provider state endpoint doesn’t really do anything.

Instead of providing the coordinates to a pact broker, we could also provide a pactUrls option pointing directly to local pact files.

A full description of the options can be found here.

If the script is run, it will load all contracts for the provider hero-provider from the specified Pact Broker and then call Pact’s Verifier. For each interaction defined int the loaded contracts the Verifier will send a request to http://localhost:3000 and check if the response matches the expectations expressed in the contract.

To make the script runnable via Node, we add some scripts to package.json:

// ./package.json
{
  "scripts": {
    "start": "node ./bin/www.js",
    "pact:providerTests": "node ./pact/provider_tests.js",
    "test:pact": "start-server-and-test start http://localhost:3000 pact:providerTests"
  }
}

The start script has already been added by the Express generator.

The script pact:providerTests runs the provider_tests.js script from above. However, this will only work when the Express server is already running.

So we create a third script test:pact that uses the start-server-and-test tool we added to our dependencies earlier to start up the Express server first and then run the provider tests.

We tell the tool to run the start task first and run the server on localhost:3000 before running the pact:providerTests task.

We can now run the provider tests and they should be green:

npm run test:pact

Conclusion

In this tutorial we went through the steps to create an Express server from scratch and enabled it to run provider tests against a Pact contract.

You can look at the example code from this tutorial in my github repo.

Written By:

Tom Hombergs

Written By:

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more than fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more