What is a Contract Test?
A Contract Test verifies that a provider has fulfilled a contract expected by a consuming service and ensures that services can communicate with each other. Contract Tests are applicable anywhere where two services communicates with each other (an API with the front-end, for example).
Contract Tests may employ a simple structure or a more complex one where many providers and consumers communicate with each other. Often the provider of one flow can be the consumer of another, as is common in a microservices architecture.
Providers and Consumers
What constitutes a provider and consumer in this context? A provider is any party that provides a service for interactions with its dependents while a consumer is any party that interacts with a dependent service through an API. Interactions between different back-end APIs are one example of a provider-consumer relationship. A browser front-end interacting with a back-end API is another.
Think of a contract as a legally enforceable agreement. Contracts represent a set of interactions with expected requests and response structures.
Throughout this document we will use the terms “pacts” and “contracts” interchangeably when referring to contracts.
A broker is where contracts are stored. It can take the form of any generic asset server but support for versioning is ideal. A good solution is to use the Pact broker which is well-suited for these exact needs and is open source. Within Docker a custom factory broker can be built.
Contract Test Hierarchy
Contract tests occupy the same space in the testing hierarchy as integration tests. Wherever the integrity of communications between services must be verified, an opportunity to create contract tests exists.
Be careful not to confuse contract tests with component tests. Contract tests do not test the functional behavior of services, only whether or not inputs and outputs of service requests conform to an expected set of attributes.
Contract Tests vs. Integration Tests
In comparison to integration tests, contract tests are advantageous in that they do not require a complete runtime environment to run in. Mock consumers and mock providers can be created to interact with real consumers and real providers, allowing tests to be run in isolation. An overview of mocking test interfaces with Pact can be found at the end of this chapter.
Integration tests expose faults in interactions between integrated units while contract tests expose faults in communication messages between services. This improves system regression detection, alerting developers early to contract faults when changes are committed to providers or consumers alike. Finally, contract tests allow consumers to develop against API definitions before the provider API has formally completed development.
Contract Tests vs. End-to-End API Tests
End-to-End functional testing will uncover discrepancies that appear in output data or the user interface. Contract tests will only check that API endpoint connections are active and functioning correctly.
By comparison, End-to-End tests are designed to be rigorous and complex which makes them time-intensive to execute. They tend to uncover significant defects that would otherwise go undetected. Contract testing, while limited in scope, is less intensive and can be performed more frequently to provide a form of smoke test coverage each time code is deployed.
Contract Tests vs. Functional Tests
Contract testing focuses on the messages that flow between a consumer and a provider while functional testing ensures that the expected system behavior has occurred.
Mocking Test Interfaces with Pact
Pact is a consumer-driven contract testing tool, meaning that contracts are generated during the execution of consumer tests. The advantage of this pattern limits testing only to producer output that is actually used. Therefore, any provider behavior not used by current consumers is free to change without breaking any tests.
Pact manages the mocking of provider and consumer objects as well as contract definitions so that you don’t have to. The basic flow of interactions between real provider/consumer objects and Pact mock provider/consumer objects is as follows:
Test-Driven Development (TDD) is employed to write the test code.
Pact produces a mocked response as if a real request was being made to a real server.
A Pact JSON file is generated.
When a test is triggered on the provider side, the Pact JSON file is loaded and a verification process determines whether or not the contract matches what a consumer is expecting. Pact generates this contract file in accordance with consumer expectations and will be used to match what the real provider server is actually sending.
Pact currently supports the following languages:
Providers and consumers can be written in different languages.
Contract testing is best used in conjunction with other types of testing to improve the overall quality of an application release. Use Contract tests to detect defects within consumer workflows, end point configuration miscommunications or unexpected payload changes resulting from provider modifications.