April 11, 2020
Second chances are a wonderful thing. In software a clean slate offers the opportunity to do things better, to improve what went wrong and start confidently with a whole load of experience.
Thankfully I have just such an opportunity with a new project at Mettle.
Naturally I want to make sure we use Pact as I believe it’s been really useful in getting us to, and keeping us in continuous delivery mode. It’s also reduced the amount of integration tests we need and meant the delivery to production is faster.
But our implementation first time around wasn’t perfect, so here’s what we’re changing (and why), now we have a second chance. The Pact docs are full of best practices - these are some of the practices we’ve learned we need to follow to get the most out of Pact.
Quick definition: consumers are components (a browser or a backend service) in a system that make requests, and providers are services which send back responses to those requests. “Pacts” are contracts created by the Pact framework.
Previously we had pacts in our consumers which changed, this was due to generated data and JWT tokens which were newly generated every time the consumer pact tests were run.
This time we will make sure we use static data and don’t add any changing JWT tokens to the pact. This means we can take advantage of Pact’s optimisation - that when a pact doesn’t change all existing verifications from providers apply to it. So the consumer can deploy immediately, rather than waiting for providers to run their verification tests and publish their results to the Pact Broker.
We used to put an Authorization header with a jwt token in the contracts but now we will exclude all authorisation and use requests filters (or a mocked authorisation service) on the provider side to ensure all requests are authenticated and authorised - as described in the Pact docs.
We can still test the contract when a token is invalid by the consumer requesting a provider state of given("An invalid authorisation token")
- although I don’t think this contract would provide much value because the consumer will only consider the status code (and making sure the provider returns a 403 status code will be covered in other unit tests).
Which raises an interesting point - I think Pact is most valuable when testing new and previously undefined contracts, and least valuable when testing establishing patterns and contracts e.g. OAuth requests/responses and http status codes such as 401 and 403. Returning a 403 when a user isn’t authorised is a well established and accepted pattern, and set out in the http standard. I would say if it’s defined in a standard then it’s probably not worth using a Pact test for it.
We used to verify the entire body of the response but from now on we will minimally verify only what is actually used by the consumer. This will allow the provider to change things which aren’t used without breaking contract with the consumer. This keeps the contract as flexible as possible whilst also providing all the confidence required to integrate correctly.
Some of our previous issues with Pact have been down to collaboration between consumer and provider teams. Sometimes pacts were published or changed without communication between teams. Our new workflow is to have both consumer team and provider team agree a contract, have the consumer team define it with a Pact test and then create a pull request which publishes it to the broker (with the branch name as the tag). This allows the provider team to ensure they meet the newly published contract and allows the consumer team to use Pact’s mock server (which generates mock responses from the published pacts) to carry on working before the provider is ready.
I believe we’re starting in a really strong place and these lessons learnt are going to make our contract testing game even stronger 💪.
Written by Phil Hardwick