Improve Testing With Pact Contracts In Swift

Colin Chan
John Lewis Partnership Software Engineering
7 min readSep 23, 2021

--

Photo by Cytonn Photography on Unsplash

How does John Lewis’ scaffold of microservice interactions keep together and not fall apart? We have many microservices providing and consuming API data with each other, and recently PACT contract testing has become an important part of our infrastructure.

In the mobile apps space, I want to give some indications on why its particularly so, and some adaptation to the third party Swift Pact contract package we use, PactSwift.

Disappointingly, as of this time, there are not that many open Pact contract frameworks in Swift that were available I could utilise in my projects. However, I wasn’t deterred, in fact I was embolden to take what was there and improve upon, because I knew the benefits would outweigh not using the technology.

Pact Overview

There are more detailed explanations on the philosophy and understanding of Pact contract testing, Pact.io is a good starting point. Here is a simple diagram that captures the basic gist of it.

Actors and Pact contract flows between them.
  • Provider. A service delivering JSON data on request.
  • Consumer. A recipient of the provider’s data. i.e. a client who will make API requests to the provider.
  • Pact Contract Broker. An intermediary service that receives, validates, and verifies evolving contracts for consumer and provider.
  • Interactions. Requests and response details, the contracted Pact matching rules.

Consuming clients create and formally define what they expect from the provider’s service in order to pass their unit tests. That definition, or contract, is sent to a broker for validation and it in turn asks the provider to verify it.
Any contract disputes are notified back to the consuming client, which has to re-form the contract, and repeat till eventually it passes verification. If a provider service wants its own changes, it needs to ensure any pre-existing contracts affected must be re-verified, and any broken contracts are notified back to their owning consumer.

Advantages

Contract testing is useful during development and maintenance of any client service that is dependent on external API providers. For unit testing, mocked API data used in Pact contracts will have a tighter, more cohesive meaning than arbitrary fragments of what you think are expected. Here are some points on why I think so.

  • Mocked JSON responses can be verified as valid at a specific point in time.
    Mock data is no longer assumed, guessed, or expected to be of a certain form. They can be checked to be actively correct and thus makes your unit tests more warranted. They will become semi integration tests, in that they are not unit isolated. There is a tested link to a dependent factor, which gives confirmation of the cohesion of the service.
  • More than just providing and testing the API’s JSON response data.
    PACT contracts cover more. They easily integrate testing of technical details of the request, response objects, such as the url, headers, query parameters, statuses.
  • Provides confidence and protection against the unexpected.
    Verified contracts act as confirmation of a time when the service worked as expected, data are correct and proven accurate, and should still be so unless something significant changes. Future unexpected changes forced upon your service can be prevented. Contracts are agreements whereby the API providers need to notify your service owners if provider’s pre-live changes could cause problems.

PactSwift Extension Improvements

There are many implementation flavours of the Pact specification for different programming languages, but in the Swift language world we are limited to PactSwift. There may be others I am not aware of, but PactSwift is as free and active as they come.

However, given what we have, PactSwift’s one way-ness and inflexibility meant there were some opportunities to engineer things better. An immediate issue was the time it took and fiddly nature to define the contract rules for each JSON property. Another was how nondescript and disconnected to the business the package’s contract matcher rules on offer were. These were areas where customised additions were added in our adoption of PactSwift.

Automated Rule Matching Application
JSON api responses can be large and unwieldy. Typically, demo examples to help you understand are small simple structures, e.g a customer with name, age, and address. In real projects, such as ours, trying to program manually a Pact contract matcher rule for each JSON property is cumbersome and error prone.

A solution we’ve introduced is auto rule matching.

Application of Pact matcher by manual conversion.

Before, using PactSwift, the, possibly large and complicated, JSON response string had to be converted to another structure with each JSON property requiring its own Pact contract matcher rule.

Now, we simply call our auto matcher routine, like this:
let matcherResponseBody = ResponseBodyAutoMatcher(to: json)

In the class, it will traverse and decode each JSON property and apply a default matcher to its value, whilst still maintaining the response shape, as it would if done manually. Following Pact recommendation to match on value type, not on actual value data, the default matcher is ‘SomethingLike’.

For large and verbose JSONs the automation is less error prone and less time consuming than by writing each matching rule for each property manually.

Override default matcher rule

Not all properties may want value type matching as the most appropriate rule. There may be a strict case that a property has to be an exact value. The auto matcher has the ability to override specific property with a specific matcher.

Code fragment to override default matching rule for the Pact auto matcher.

Before running against the target JSON, the auto matcher object can be configured to have a set of overriding actions. In the example above it will use PactSwift’s ‘EachLike’ matcher instead of ‘SomethingLike’. During the automation process, when it reaches any of the pre-configured defined key paths then it uses the corresponding matching rule instead.

This give us more flexibility and adaptability for writing more precise contract rules in conjunction with the time saved.

Custom matcher rules

One of the versatile rule matchers that PactSwift implements is ‘Regexlike’. It uses regular expression to match it against the property value. Utilising that capability, it is very simple to expand out many ‘friendlier’ matchers that encapsulate more precise rules.

Code example of the custom enum Pact matcher signature.

This is a simple example of a custom matcher that matches enum properties, fruit’s value must be any of the given values. Inside it is defined with the ‘Regexlike’ matcher. Its nice because it removes the necessity of working out or copy pasting the regex each time, and offers a simple list argument interface to define valid values the property should be.

This contract rule solves the problem of decoding enum property values whereby the recipient variable or business logic must be constrained to a fixed set of possible values, otherwise there might be a decoding error. Now, if the API provider wants to add or delete their enums they must pre-warn you beforehand to give you time to accommodate those new changes and adjust the contracts before they can go live.

List of other custom Pact matchers that are more specifc to business context.

Furthermore, we can create as many custom matchers in the context of our business domain. The above gives some indication of such examples.

Note, the above are matching not on exact value but on formatted type, using regular expression. This makes out Pact contracts more robust, as matching on a string, boolean, integer type may be too vague and exact value matching is too strict. Just like Goldilocks, we need something in the middle.

Encapsulating all this in a reusable object with its library of custom Pact matching rules encourages reusability and maintainability. The sharing of rules means any business rule change can be easily applied and contracts can simply reapply for verification to adopt the updates.

Summary

I hope I have shown how much benefit Pact contract testing can have in building and maintaining our microservices.

PactSwift is one implementation library of others and as I have shown, where there are limitations once can extend it to get around them. Improvements and expansion of contract matching rules will enrich your usage of Pact contracts for the better.

Our services will behave as planned if their API providers always promise to return expected data types and structures when asked to in the same way. But if the provider needs to break that promise then it must notify those concerned beforehand and get their approval before committing to the change.

Finally, its been a good learning experience introducing Pact contract testing to our Swift projects. The opportunities will continue as we discover and solve further challenging problems in order to make our infrastructure better.

Looking for more?

For another good article on contract testing please see Consumer Driven Contract Testing — A scalable testing strategy for Microservices, as well as other interesting articles at john-lewis-software-engineering.

The John Lewis Partnership is always looking for great engineers to join us. Our latest vacancies can be found at www.jlpjobs.com.

--

--

Colin Chan
John Lewis Partnership Software Engineering

Product Engineer at John Lewis and Partners. Currently, IOS developer on the John Lewis mobile team. Before, Java developer on many adventures for many years.