Consumer driven contract testing — Pact

In this post, I am going to walk you through how to do unit testing when the class-under-test is a http-services consumer.

Unit Testing

When it comes to unit testing, no matter if you choose TDD or BDD style (the framework I’ve been using is SpecsFor), the very first thing you need to ask yourself before writing any tests is which class you want to test, called Class Under Test (CUT). In addition to that, it may not be worth testing every class and the classes you need to test contain critical / complicated logic.

In order to focus on only testing the logic in one CUT (one object is being tested at a time), what you do not want to test is whether the objects that CUT depends on and interacts with are working properly or not. Therefore, you need a way to isolate CUT from its dependencies.

Mock frameworks (one of them that is easy to use is NSubstitute) were introduced to

  • create a mock object for a given interface
  • set its contracts / expectations (like a guid is returned when calling AddToCart method)

Then you replace the dependencies with their mocks when testing the CUT. You may also be interesting in the differences between mocks and stubs.

Unit Testing when HTTP gets involved

When testing a class, called consumer, that uses http-based services, with the same thinking, you want to have a framework to create a mock http endpoint for a given contract

  • the HTTP requests the consumer will make to a service (like /api/tags)
  • the HTTP responses the consumer expects back (i.e. [{“Name”:”c#”,”NumberOfPosts”:123},{“Name”:”.net”,”NumberOfPosts”:100},{“Name”:”pact”,”NumberOfPosts”:101}])

Then you replace the services’ endpoints with their mocks’ endpoints when testing the consumer.

When testing a service, you want to have a framework to use the same contract to ensure / verify it does provide the response the contract specifies for the request when the service is set up with certain states (Pact framework uses provider states to set up such states)

Introducing Pact

Pact is such framework (its .NET implementation is here) that was developed around this idea. Thanks REA and SEEK for open source it! Watching a great talk about it from its original developers.

Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.

The use of Pact framework is straightforward, but there are a few new concepts you may need to look at before writing your tests.

The contract is driven by consumers, so it is called consumer driven contract.

  • It is shared between consumers and their service providers (because it is a contract)
  • It is described as a set of interactions in a JSON file using its own DSL whenever consumers’ tests are run
  • Once it is modified (on the consumers’ side), their service providers need to re-run tests against the new contracts to see if the changes to the contract are supported.
  • Once service providers introduce changes, they need to re-run tests against the existing contracts to make sure changes are not breaking changes.

Show me the code (C#)

There is a http-based service returning a list of tags using ASP.NET WebApi.

This service offers a proxy or client library, TagsServiceProxy, for any consumers to interact with it.

There is a consumer that has logic to only return popular tags. The logic is what we want to test at the consumer side. How and whether the service works is not a concern when testing the logic.

With Pact, we configure it to create a mock http endpoint and set it up with expected request and response, and let TagsServiceProxy use it.

Once the contract is generated, we then configure our WebApi to use tests data (and mocks as its dependencies using mock frameworks like NSubstitute), and ask Pact to verify whether WebApi returns expected response for the requests.

Consumer driven contract testing is a very interesting ideas. It not only helps in unit testing but also import consumer expectations into services so that services can make sure the changes are not breaking ones.