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:
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
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;
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
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();
});
});
});
});
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',
})
And the mock server will respond with the expected response.
.willRespondWith({
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: {
population: MatchersV3.number(7794798739),
},
})
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
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
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}`);
});
To run the server, run the following command:
node src/server
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();
});
});
});
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
Congratulations! You have successfully written your first consumer driven contract test and provider verification test using Pact.
Top comments (1)
Thank you for writing this. My last haven. Amazing, indeed.