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]); }); });