The Pros and Cons of Using Pact for Contract Testing
In my previous blog post , I spoke about the importance of Contract Testing in the software development space. A tool that can assist in testing these service interactions certainly helps increase your ability to grow your contract testing coverage. And this is where Pact comes in.
Pact is an open-source contract testing tool used to test interactions between services based on contracts. It is particularly useful in microservices architectures where different services (or APIs) communicate with one another. Pact helps ensure that these services can interact correctly by testing the contracts between them.
As a reminder, here is a brief overview of the concept of Contract Testing and how Pact works in helping achieve these contract validations between services.
Key Concepts
How Pact Works
How Pact Helps with Contract Testing
Example Workflow
How to Use Pact
It’s a concept that is easy to explain, but how you go about structuring your contract tests and making use of Pact to do so is key. Contract testing works best when integrating directing into your CI/CD pipelines so I also want to unpack how contract tests with Pact can be easily integrated into your delivery pipelines as well.
Below is an example of how to set up a Pact script for testing APIs and integrate it into a CI/CD pipeline.
1. Pact Example Script (Node.js/JavaScript)
We will start by setting up a script for both the consumer and the provider.
Pact Consumer Test (For Consumer-Driven Contract)
In this example, the consumer makes an HTTP request to the provider (an API), expecting a particular response.
Install dependencies:
In the below examples, I’ve included a quick bash script that makes use of npm can do this. Even if not using Node.JS though you can use similar commands to install the dependencies from other package management software.
npm install @pact-foundation/pact @pact-foundation/pact-node axios mocha --save-dev
Consumer Pact Test (consumer.test.js):
All other non-install code makes use of JavaScript.
const path = require('path');
const { Pact } = require('@pact-foundation/pact');
const axios = require('axios');
const { expect } = require('chai');
?
const consumerPact = new Pact({
? consumer: 'UserService',??? // Name of the consumer
? provider: 'AuthService',??? // Name of the provider
? port: 1234,???????????????? // Port Pact will run the mock server on
? log: path.resolve(process.cwd(), 'logs', 'pact.log'), // Logging
? dir: path.resolve(process.cwd(), 'pacts'),?????????? // Directory to store Pact files
? logLevel: 'INFO',
});
?
// Define your expected interaction
describe('Pact with AuthService', () => {
? before(() =>
??? consumerPact.setup().then(() => {
????? consumerPact.addInteraction({
??????? uponReceiving: 'a request for a valid user',
??????? withRequest: {
????????? method: 'GET',
????????? path: '/user/1',
????????? headers: { 'Accept': 'application/json' },
??????? },
??????? willRespondWith: {
????????? status: 200,
????????? headers: { 'Content-Type': 'application/json' },
????????? body: { id: 1, name: 'John Doe', email: '[email protected]' },
??????? },
????? });
??? })
? );
? it('should receive a valid user', async () => {
??? const response = await axios.get('https://localhost:1234/user/1');
??? expect(response.status).to.eql(200);
??? expect(response.data).to.deep.include({
????? id: 1,
????? name: 'John Doe',
????? email: '[email protected]',
??? });
? });
?
? // Verify the contract
? after(() => consumerPact.verify().then(() => consumerPact.finalize()));
});
This above consumer test does the following:
Pact Provider Test (Provider Verification)
This is simply the setup of the consumer. Now, let's verify the provider (API) against the contract generated by the consumer.
Install dependencies:
npm install @pact-foundation/pact-provider-verifier express --save-dev
Provider Pact Verification (provider.test.js):
领英推荐
const { Verifier } = require('@pact-foundation/pact');
const path = require('path');
const app = require('./provider'); // Your actual provider API implementation
?
describe('Pact Verification', () => {
? it('should validate the expectations of UserService', () => {
??? return new Verifier({
????? providerBaseUrl: 'https://localhost:3000', // Where your provider is running
????? pactUrls: [path.resolve(process.cwd(), './pacts/userservice-authservice.json')], // Pact file generated by the consumer
????? provider: 'AuthService',
????? providerVersion: '1.0.0',
??? })
????? .verifyProvider()
????? .then(output => {
??????? console.log('Pact Verification Complete!');
??????? console.log(output);
????? });
? });
});
2. Setting Up Pact in a CI/CD Pipeline
Now that we have a contract test written in Pact, we will look at how we can add this to your CI/CD pipelines using YAML.
To integrate Pact into a CI/CD pipeline, follow these steps:
1. Consumer Test in CI Pipeline
Example GitHub Actions for Consumer:
name: Consumer Pact Test
?
on:
? push:
??? branches:
????? - main
?
jobs:
? test:
??? runs-on: ubuntu-latest
??? steps:
????? - uses: actions/checkout@v2
????? - name: Set up Node.js
??????? uses: actions/setup-node@v2
??????? with:
????????? node-version: '14'
????? - run: npm install
????? - run: npm test # Runs the consumer Pact test to generate the contract
????? - name: Upload Pact contract
??????? uses: pact-foundation/pact-action@v2
??????? with:
????????? pact-files: './pacts/*.json'? # Pact file to publish
????????? pact-broker-url: ${{ secrets.PACT_BROKER_URL }}? # Your Pact Broker URL
????????? pact-broker-token: ${{ secrets.PACT_BROKER_TOKEN }}? # Pact Broker token
This workflow:
2. Provider Verification in CI Pipeline
This is only the first part of the pipeline setup though. After the Pact file is published, the provider needs to verify it.
Example GitHub Actions for Provider:
name: Provider Pact Verification
on:
? workflow_run:
??? workflows: ["Consumer Pact Test"]
??? types:
????? - completed
?
jobs:
? verify:
??? runs-on: ubuntu-latest
??? steps:
????? - uses: actions/checkout@v2
????? - name: Set up Node.js
??????? uses: actions/setup-node@v2
??????? with:
????????? node-version: '14'
????? - run: npm install
????? - name: Verify Pact contract
??????? run: npm test # Runs the provider Pact verification
This workflow:
3. Pact Broker
A Pact Broker is essential for sharing contracts between the consumer and provider. It allows continuous integration of Pact tests by:
You can host your own Pact Broker or use a service like the Pactflow.io hosted broker.
Summary of Steps
By setting this up, your API consumers and providers can evolve independently while maintaining reliable communication.
Pros and Cons of Pact
Now that we have a good idea of how to use Pact to perform our contract tests, let’s summarise the list of procs and cons we get out of the tool
Pros of Pact:
Cons of Pact:
Summary
Contract testing plays an important role in allowing you to have confidence in your microservices testing and ensuring that the dependencies still meet the expectations of a service. Utilizing a tool like Pact to help with this makes it easy to write your contract tests and improves your ability to deploy independently across your different services and APIs.