-
a formal agreement between individuals or parties.

late Middle English: from Old French, from Latin pactum ‘something agreed’, neuter past participle (used as a noun) of paciscere ‘agree’.
Pact from Pact Foundation is designed to be a dependable testing method for integration points, such as API’s and microservices, for development teams concerned with message conformity. In the past, teams would ensure conformity with integration tests (a loaded term with many different meanings). These tests were expensive to create, and often unwieldy.
Pact aims to make it easier to ensure conformity with contract tests that make it possible to ensure a contract is being followed without having to wire everything to everything else and deploy it all first.
There is an hilarious analogy on the https://docs.pact.io website: “do you set fire to your house to test your smoke alarm? No, you test the contract it holds with your ears by using the testing button”. – (https://docs.pact.io)
In order to provide this sort of contract testing, Pact checks each application in isolation checking the messages it sends out, and how it behaves to certain expected messages coming in, and documenting all of this in a ‘pact file’ – the contract.
If you have application (A) providing a service to application (B), think of application (A) as the provider and application (B) as the consumer. This is the simplest way to look at it, although of course you can/will have multiple providers and consumers. Pact is a code-first ‘consumer-driven’ contract test tool. That means that any part of the providing service your consumer doesn’t use can change without an impact, as only the parts your consumer cares about are tested. This makes the contract test far less brittle than traditional integration tests. Pact is ‘contract by example’.
Personally, I found it a little hard to get my head around the design of Pact at first. I even doubted the value of a consumer-driven contract. I was more used to the idea of provider-driven integration testing. There are also quite a few moving parts involved, which takes some time to pin down in an abstract image in your mind – at least, my mind. It’s one of those tools that you really get to grips with once you create your first meaty test.
Having said that, the documentation is really very good.
The first thing to do is to get to grips with the terminology:
- consumer – an application that uses either data or functionality from another application. For example: if your application uses HTTP and is the consumer of another application, your consumer initiates the HTTP request. It doesn’t matter what direction data flows. You can also have applications that utilise queues, if your application falls into this category, your consumer is the application that reads the message from the queue.
- provider – otherwise known as a service, that serves either data or functionality to other applications. So for example, if your application uses HTTP, the provider is the application that responds to your application. If you use queues, the provider is the application that puts messages on the queue.
- pact – the contract between the two entities described above. This is described through a batch of interactions. For HTTP, the interaction consists of an expected request (i.e. what is the minimal expected request from this consumer) and a minimal expected response (the very least that the consumer wants back from the provider). For queues it would be a description of the minimum expected message or pieces of a message that the consumer needs.
Given the above description, the consumer needs to run its test first, as it needs to generate the pact file / contract. Once that pact file is generated, the provider has something to verify against.
Overview of how it works
Essentially, the consumer uses a mock provider, and the provider uses a mock consumer.
Consumer test
So, for HTTP for example, in the first instance, the consumer defines an interaction (expected request/minimal expected response).
e.g.
import { GOOD_LOGIN_REQ, GOOD_LOGIN_RESP } '../test-data/login-req-resp.json';
describe('Login service', () => {
const mockProvider = new Pact({
host: 'localhost',
port: '4444',
consumer: 'mymadeupsite',
provider: 'myloginservice',
dir: resolve(__dirname, pathToWherePactFileGoes),
logLevel: 'error',
log: resolve(__dirname, 'logs', 'mylogintest.log'),
});
beforeAll(() => mockProvider.setup());
afterAll(() => mockProvider.finalize());
describe('a successful login interaction', () => {
const successfulLoginInteraction: InteractionObject = {
uponReceiving: 'a good login request',
withRequest: GOOD_LOGIN_REQ,
willRespondWith: GOOD_LOGIN_RESP,
} as InteractionObject();
beforeAll(() => {
return mockProvider.addInteraction(successfulLoginInteraction);
});
.... // here then you have your tests that call a real service
.... // the net result of which is Pact captures the request
where the GOOD_LOGIN_REQ might look something like:
export const GOOD_LOGIN_REQ = {
method: 'POST',
path: '/login',
body: {
username: 'fakename',
password: 'fakepwd',
},
headers: {
Accept: 'application/json',
},
};
and the GOOD_LOGIN_RESP might look something like:
export const GOOD_LOGIN_RESP = {
status: 200,
body: {
customer: mockCustomer,
},
};
It then sends a real request to the mock provider which Pact compares to the expected request. The mock provider then replies with the minimal expected response if it liked the request. The consumer code confirms that it liked the response. If entirely happy, the consumer test will pass.
For provider testing, a mock consumer sends an expected request (defined in the interaction) to a real provider. This elicits a real response. That real response is then compared by Pact to the minimal expected response defined in the interaction. If each response to the provided request(s) matches the minimal expected response, then Pact is happy.
The provider test is really all about defining where the pact file can be found, setting up any state handlers, and running the verifier. You may have extra setup depending on your application; such as Inversify containers and mocks and stubs etc… but that is a separate issue.
e.g.
describe('Provider test for login', function() {
before(done => {
// ... container setup
});
it('should verify the contract', function() {
const opts: VerifierOptions = {
provider: 'loginservice',
providerBaseUrl: PROVIDER_BASE_URL,
providerVersion: '1.0.0',
pactBrokerUrl: PATH_TO_PACT_BROKER,
publishVerificationResult: true,
logLevel: 'debug',
stateHandlers: {
'login is successful': () Promise => {
return mockAdapter.onPost(`/usercheck`).reply(200, {'userOk': true });
},
},
};
return new Verifier(opts).verifyProvider()
.then(() => {
console.log('Pact verification complete');
});
});
Now, it’s important at this point to understand something fundamental to the design of Pact. The contract test must be data independent. Pact shouldn’t care about specific data, it cares about the ‘nature’ of data. So for instance, you wouldn’t ask Pact to verify that a response that contains say, a persons age, was precisely 44. You would ask Pact to verify the response contains an age field which has a number as its value. Which brings us on to….
For expected requests, it’s advisable to make it as explicit as possible, as specific as possible – since you are in charge of the request. Pact allows for extra headers but not for unexpected values.
e.g.
export const SESSION = {
method: 'HEAD',
path: '/session',
headers: {
},
cookies: [
'__session_cookie=mock_session;',
],
};
As for responses, Pact allows loose matching. Remember I mentioned Pact shouldn’t care about specific data? Well for responses it enables you to declare the shape and flavour of data without getting stuck on specifics, through matchers. In the example I gave above where the response should contain a field with a persons age in it, we are interested in the field being a number, not precisely 44. In that case, we can use the ‘like’ matcher. In the response, we can say the age field should be ‘like’ 44, “like(44)”, and if the response comes back with the age field holding a value of, say, 65, well that’s just fine. Next time if the provider responds with say, 19, that’s also fine.
e.g. ‘id: like(‘FDJKJ3849SUSHS’),’.You can use the ‘like’ matcher on single fields or block objects in the JSON. e.g.
customer: like({
username: string('username'),
password: string('password'),
}),
You can use the ‘matcher’ er, matcher… and benefit from using regular expressions for fields in the response.
e.g.
customerState: regex({
current: 'ACTIVE',
matcher: 'ACTIVE|UNSUBSCRIBED',
}),
State
This is all well and good, but the use of interactions implies that each interaction is independent – and they are. But that’s ok. If you have interactions that depend on each other, you can use something called ‘provider states’ to accomplish this. These states are prerequisites, or testing pre-conditions to get the provider to generate the expected response. You can setup situations where certain data is present to create a certain scenario. This setup is done before the interaction is played out.
One of the draw-backs of Pact vs traditional integration tests is that key information is spread across multiple builds, instead of being in one place. To get around this, there is the concept of the Pact Broker. Think of it as a repository for your pact files (contracts) with added jazziness in the form of test results. You can specify the Pact Broker as the source of the pact file so that the provider test knows where to find the contract it needs to verify against, with the only prerequisite being the obvious need to publish that pact file to the broker beforehand. There is a bit of management involved with the Pact Broker – you have to deploy it and manage it. However, it brings big benefits such as:
- you can release with certainty and speed because you can deploy your services independently
- you can use the Pact Broker to see what versions of your application is compatible with another application
- you can see how your services and consumers relate to one another
- versions your contracts
- gives you one place to share the test results
- helps you to ensure backwards compatibility between applications
- serves living documentation for your application(s) API
- provides a RESTful API to allow you to publish your contracts and retrieve them
- provides for application tagging, e.g. ‘prod’, ‘featureX’
- allows for notifications or event-driven actions like running builds, for example when a pact file changes
It may be the way my team implemented Pact, or it may just be a sticking point in my mental imagery of the inner-workings of Pact, but to be honest, I found it quite clunky at first.
Some problems that we experienced were down to our tagging approach of pacts in the Pact Broker. Tests would pass, but we discovered some green results were hiding the fact that no test was actually executed, due to it not finding a pact for the consumer-provider pair. We also discovered that tests were running against older pact versions, since no new pact files were being published due to an issue in our pipeline. This led me to distrust green test reports. I can’t say this is a problem with the product… but it speaks to the perceived clunkiness I mention earlier.
With due care and diligence, Pact can definitely save on the cost of writing integration tests that rely upon a fully deployed set of dependencies, and it does help to provide confidence for the contract between providers and consumers that are in active development. Your team starts to think about using the requirements of the consumer to drive the development of the provider. Crucially, you find out if you’ve broken the contract BEFORE you deploy. It also necessarily improves communication between teams if it is to be successful.
Things Pact isn’t designed to do:
- Of course, it’s no use if one side of the interaction doesn’t use Pact.
- It isn’t designed for testing API’s where consumers can’t be easily identified.
- If you can’t load data into the provider without using the actual API you’re wanting to test, Pact isn’t suitable.
- The team creating providers shouldn’t use Pact to test their functionality, as mentioned above, Pact is not about that. Other tests must cover functionality.
- OAuth providers are – for good reason – stable API’s. They aren’t driven by consumers requirements, so aren’t a good candidate for Pact.
If the above bullet-points don’t apply, and you either provide a service or consume it, I can tell you that our team (once we better understood it) definitely benefited from using Pact. Writing a couple of tests really solidified our grasp of it as a tool and what it is good for. Integrating the Pact tests into our CI/CD pipeline made sure we didn’t deploy if a contract was broken.
Most of our consumers and providers were internal. However, some were 3rd parties. It was crucial that we had communication lines open with these teams (see the bullet-points above) and that requirements drove the development.
There is so much more to Pact than befits the scope of this post; suffice to say I went from not fully understanding the value of it and finding it clunky (perhaps a psychological response to my lack of understanding), to advocating it to any team(s) where applications are consumed and the criteria for Pact being the right tool are met.
For much more functionality and documentation please visit the Pact Foundation website, and see for yourself how you can increase productivity with this tool.
source: https://docs.pact.io


Like what you see? Please support us with a donation.
Support Cognito Square Ltd with a one-off donation in order to help us continue to publish helpful QA Automation and Agile Project Delivery articles.
£5.00