An introduction to Pact testing in .Net Core
Pacticipants ready?? (If you were around in the 90s and know the reference to Gladiators…)
Over the past couple of years, I’ve heard a lot of people talking about Pact and testing APIs. A shout-out to the CTO at Zava for sending me the first link on the topic. At the time, I didn’t fully appreciate the value it could bring. It wasn’t until I started working at ASOS that I saw the true value and my journey towards implementing Pact begun.
For those that don’t know, Pact testing is a framework that helps you implement contract tests between APIs. The benefit of this is you get fast feedback on any breaking changes that may occur when contracts change between APIs. It complements your existing test suite, by reducing reliance on integration tests, and as part of our testing process we can use contract tests to check for any regressions in API contracts. An added bonus is that it helps to give us a better understanding of how our consumers use the API that we support.
Adopting contract testing within my domain was an easy choice but implementing it into an existing code base was not. I will explain why in this post. Our front-end was using Dot net core — MVC Razor views, this was our consumer — whilst the API was using an existing .Net Framework 4.6.2. Contract testing here is applied to catch regression issues and provide visibility to breaking contract changes earlier in our deployment pipeline. My aim was to get an end-to-end journey up and running (to enable others to adopt the framework easily). I started by creating a simple consumer contract using a test user.
My first obstacle with C# was strongly typing a JSON object that includes Pact matchers. I started by constructing my C# object using the dynamic object type, copying each field from the strongly typed object created within the source code to an Expando Object:
var obj = new Customer(){
Id = 0,
Name = "Wolf",
Dob = "11/01/2018"
};
dynamic myDynamicObject = new ExpandoObject();
myDynamicObject.id = Match.Type(obj.Id);
myDynamicObject.name = obj.Name;
myDynamicObject.dob = Match.Type(obj.Dob);
Working with complex JSON objects
The size of each JSON response object meant this was a time-consuming task. I missed a few mandatory fields and constructed objects incorrectly. I questioned whether we care if a string field is exactly ‘Wolf’ or I should just be comparing object types, and applying Postel’s Law here instead (Postel’s Law applies to the contract itself but could be applied here too: ‘be liberal in what you accept’). This resulted in me using reflection, and sped up the process a little:
Pact matchers can be added for each property type using reflection and different object types can be catered for at the same time. It can then be serialised to a JSON response string and finally parsed to a JObject type for parsing:
This reduced the arbitrary task to construct the dynamic object and I could construct the C# object in the native language without the hassle.
Manipulating provider states
We moved on to the provider tests within the API project. In part one we created our contract with our test user (I created this user manually within our data store in our system integration test (SIT) environment). The customer looks a bit like this:
{
id: 0,
name: "Lightning",
dob: "01-01-0001T00000Z",
address: {
id: 0,
addressLine1: "Hang tough road",
phoneNo: 01324140942 }}
Before I could run the provider tests, I faced the second obstacle with .Net implementation of Pact — provider states. My assumption was that I would be able to setup a mock data store object and manipulate my request or query to point to this mock user. Ideally there would be an option for the API to point to a mock server (.Net Core TestServer for example), which would use the application Startup class with the appropriate mocks injected through dependency injection. I then started to explore how provider states had been implemented in .Net. In retrospect, this was probably the wrong order of events, but it led me to contribute a similar feature to the Pact .Net project. I discovered the middle-ware only offers the ability to add events prior to the API request, e.g. creating a user.
Our requirements involved a unique ID to be in the API query and the request body to also contain the unique ID. Therefore, we needed to alter the contract (at this point the contract lived in the Pact broker). I concluded the way to work around this was to call Pact broker API using HttpClient, manipulate the request body and query within the contract file and output a new contract file for use with Pact methods. I did this by de-serialising the JSON to a custom C# Model (using Pacticipants & provider service Models):
Once the contract has been de-serialised then the query and request can be modified for the scenario described, where the ID needs to be modified for the user. Finally, we serialised the object back to a JSON string and outputted this to a new file, which can be pointed to the `PactVerifier.PactUri(‘pactFile’)` method.
.Net Core implementation
To make the methods discussed available across the business, for both .Net Core and .Net Framework projects, the framework was implemented using .Net Standard. In upgrading from .Net Framework to .Net Core, I replaced RestSharp with HttpClient to call Pact broker, to retrieve the contracts, working with JSON utilising the Newtonsoft.Json package in serialising and de-serialising objects (soon to be packaged with .Net Core and not necessary).
In conclusion
Pact testing in .Net Core requires some workarounds and a bit of force, like charging your way through the Gauntlet (another Gladiator reference…). For me, the true value in the tool comes in the collaboration across teams and keeping an eye on your consumers — as manipulating the contract is risky in terms of validity.
Lewis Prescott is a QA Engineer at ASOS.com. When he’s not testing, he’s teaching children to code at a local Code Club, experimenting with new testing frameworks or challenging himself physically (he ran his first marathon this year). Checkout my course on End to End Testing in Microservices. Find me at www.pactman.co.uk.