I like Contract Testing! I added a contract test with PACT-js and Jest for my consumer like this:
Installing PACT
- Disable the ignore-scripts setting:
npm config set ignore-scripts false - Ensure build chain is installed. Most linux based CI/CD agents have this out of the box. My local dev machine runs Windows; according to the installation guide for gyp the process is:
- Install Python from the MS App store. This takes about 5 minutes.
- Ensure the machine can build native code. My machine had Visual Studio already so I just added the ‘Desktop development with C++’ workload using the installer from ‘Tools -> Get Tools and Features’ This takes about 15 – 30 minutes
npm install -g node-gyp
- Install the PACT js npmn package:
npm i -S @pact-foundation/pact@latest - Write a unit test using either the V3 or V2 of the PACT specification. See below for some examples.
- Update your CI build pipeline to publish the PACT like this:
npx pact-broker publish ./pacts --consumer-app-version=$BUILD_BUILDNUMBER --auto-detect-version-properties --broker-base-url=$PACT_BROKER_BASE_URL --broker-token=$PACT_BROKER_TOKEN
A V3 version of a PACT unit test in Jest
//BffClient is the class implementing the logic to interact with the micro-service.
//the objective of this test is to:
//1. Define the PACT with the microservice
//2. Verify the class communicates according to the pact
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import path from 'path';
import { BffClient } from './BffClient';
// Create a 'pact' between the two applications in the integration we are testing
const provider = new PactV3({
dir: path.resolve(process.cwd(), 'pacts'),
consumer: 'MyConsumer',
provider: 'MyProvider',
});
describe('GET /', () => {
it('returns OK and an array of items', () => {
const exampleData: any = { name: "my-name", environment: "my-environment", };
// Arrange: Setup our expected interactions. Pact mocks the microservice for us.
provider
.given('I have a list of items')
.uponReceiving('a request for all items')
.withRequest({method: 'GET', path: '/', })
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: MatchersV3.eachLike(exampleData),
});
return provider.executeTest(async (mockserver) => {
// Act: trigger our BffClient client code to do its behavior
// we configured it to use the mock instead of needing some external thing to run
const sut = new BffClient(mockserver.url, "");
const response = await sut.get()
// Assert: check the result
expect(response.status).toEqual(200)
const data:any[] = await response.json()
expect(data).toEqual([exampleData]);
});
});
});
A V2 version
import { Pact, Matchers } from '@pact-foundation/pact';
import path from 'path';
import { BffClient } from './BffClient';
// Create a 'pact' between the two applications in the integration we are testing
const provider = new Pact({
dir: path.resolve(process.cwd(), 'pacts'),
consumer: 'MyConsumer',
provider: 'MyProvider',
});
describe('GET', () => {
afterEach(() => provider.verify());
afterAll(() => provider.finalize());
it('returns OK and array of items', async () => {
const exampleData: any = { name: "my-name", environment: "my-environment", };
// Arrange: Setup our expected interactions. Pact mocks the microservice for us.
await provider.setup()
await provider.addInteraction({
state: 'I have a list of items',
uponReceiving: 'a request for all items',
withRequest: { method: 'GET', path: '/', },
willRespondWith: {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: Matchers.eachLike(exampleData),
},
})
// Act: trigger our BffClient client code to do its behavior
// we configured it to use the mock instead of needing some external thing to run
const sut= new BffClient(mockserver.url, "");
const response = sut.get()
// Assert: check the result
expect(response.status).toEqual(200)
const data: any[] = await response.json()
expect(data).toEqual([exampleData]);
});
});