API Contract Testing Made Easy

As software becomes increasingly complex, more and more companies are turning to APIs as a way to organize and manage their application’s functionality.  Instead of being one monolithic application where all changes are released at once, now software can be made up of multiple APIs that are dependent upon each other, but which can be released separately at any time.  Because of this, it’s possible to have a scenario where one API releases new functionality which breaks a second API’s functionality, because the second API was relying on the first and now something has changed.

The way to mitigate the risk of this happening is through using API contract tests.  These can seem confusing: which API sets up the tests, and which API runs them?  Fortunately after watching this presentation, I understand the concept a bit better.  In this post I’ll be creating a very simple example to show how contract testing works.

Let’s imagine that we have an online store that sells superballs.  The store sells superballs of different colors and sizes, and it has uses three different APIs to accomplish its sales tasks:

Inventory API:  This API keeps track of the superball inventory, to make sure that orders can be fulfilled.  It has the following endpoints:

  • /checkInventory, which passes in a color and size and verifies that that ball is available
  • /remove, which passes in a color and size and removes that ball from the inventory
  • /add, which passes in a color and size and adds that ball to the inventory

Orders API:  This API is responsible for taking and processing orders from customers.  It has the following endpoints:
  • /addToCart, which puts a ball in the customer’s shopping cart
  • /placeOrder, which completes the sale

Returns API:  This API is responsible for processing customer returns.  It has the following endpoint:
  • /processReturn, which confirms the customer’s return and starts the refund process
Both the Orders API and the Returns API are dependent on the Inventory API in the following ways:
  • When the Orders API processes the /addToCart command, it calls the /checkInventory endpoint to verify that the type of ball that’s been added to the cart is available
  • When the Orders API processes the /placeOrder command, it calls the /remove command to remove that ball from the inventory so it can’t be ordered by someone else
  • When the Returns API runs the /processReturn command, it calls the /add command to return that ball to the inventory
In this example, the Inventory API is the producer, and the Orders API and Returns API are the consumers.  
It is the consumer’s responsibility to provide the producer with some contract tests to run whenever the producer makes a code change to their API.  So in our example:
The team who works on the Orders API would provide contract tests like this to the team who works on the Inventory API:
  • /checkInventory, where the body contained { “color”: “purple”, “size”: “small” }
  • /remove, where the body contained { “color”: “red”, “size”: “large” }
The team who works on the Returns API would provide an example like this to the team who works on the Inventory API:
  • /add, where the body contained { “color”: “yellow”, “size”: “small” }
Now the team that works on the Inventory API can take those examples and add them to their suite of tests.  
Let’s imagine that the superball store has just had an update to their inventory. There are now two different kinds of bounce levels for the balls: medium and high.  So the Inventory API needs to make some changes to their API to reflect this.  Now a ball can have three properties: color, size, and bounce.  
The Inventory API modifies their /checkInventory, /add, and /remove commands to accept the new bounce property.  But the developer accidentally makes “bounce” a required field for the /checkInventory endpoint.  
After the changes are made, the contract tests are run.  The /checkInventory test contributed by the Orders API fails with a 400 error, because there’s no value for “bounce”.  When the developer sees this, she finds her error and makes the bounce property optional.  Now the /checkInventory call will pass.  
Without these contract tests in place, the team working on the Inventory API might not have noticed that their change was going to break the Orders API.  If the change went to production, no customer would be able to add a ball to their cart!
I hope this simple example illustrates the importance of contract testing, and the responsibilities of each API team when setting up contracts.  I’d love to hear about how you are using contract testing in your own work!  You can add your experiences in the Comments section.

7 thoughts on “API Contract Testing Made Easy

  1. Nitbuntu

    That was interesting and quite clear.
    Just wondering whether you’ve tried this approach and what software were you using? I hear a lot about Pact, but there’s also Spring Cloud Contracts and apparently Postman can also be used in this way.

  2. Abhijeet Vaikar

    The pact documentation talks about comparison between contract testing and functional testing here: https://docs.pact.io/consumer/contract_tests_not_functional_tests

    and I have not been able to wrap my head around it at all. If you get it, could you maybe help me understand what could be the difference between contract and functional testing of a service?

    Also in contract testing is it important for the accuracy of the values in the service's response or is it just the response structure that matters?

  3. Kristin Jackvony

    Great question, Abhijeet! I think the difference between contract tests and functional tests is that the contract tests only focus on the relationship between the two APIs. In the example in this blog post, we have the Returns API with the /processReturn endpoint. That endpoint adds a ball back to the inventory and starts the process to refund the money to the customer. When the Returns API team provides contract tests to the Inventory API, they're not going to include any tests for refunding money, because that has nothing to do with the Inventory API. Any tests for refunding money would be functional tests for the Returns API.

  4. Sunny

    Does that mean, we need another layer to support contract tests, other than functional. Why I am saying is because let's say I have a functional suite using RestAssured , so now, I need to use another tool/lib for contract tests?

  5. Kristin Jackvony

    Hi Sunny- no, contract tests don't need another layer; you can simply add new API tests into your existing framework. While there are tools that use mocking or virtualization to simulate an APIs response, you don't have to use them, and you can instead rely on calls to the real service.

Comments are closed.