#58: Consumer-driven Contracts: TDD between services
Consumer-driven Contracts is an approach to testing integration between services. In a distributed system, many components talk to each other. Typically via request/response protocols or message queues. The client must know and understand the API provided by the server. What kind of endpoints are available, what formats, request/response schema. Without consumer-driven contracts (CDC for short), we are often reckless when it comes to testing. Maybe we have a bunch of smoke tests against a mocked server. Maybe we copy-paste typical responses from the server’s documentation. But both client and server can evolve, breaking the integration in unexpected ways. CDC attempts to codify the API without explicit schema and coordination.
First, the consumer (client) creates a contract. It’s basically a set of requirements for the server. The contract typically contains sample requests and responses. So it’s far from a comprehensive schema, describing the whole API.
The contract is written in a technology-agnostic language. However, it’s then translated into two independent artifacts. One of them is consumer stubs. Basically, on the client-side, CDC framework builds a stub server. Integration tests in the consumer may use that server, just as if it was a real one. Stubs are no different from simply mocking the server we need for tests. If your code relies on some 3rd party API, you mock it out. So, what’s the big deal?
Well, contracts are also known to the provider. And the provider must fulfil them. Remember, I said that CDC framework produces two artifacts:
- stubs on the consumer
- …and complete tests on the provider side!
OK, “complete tests”? Yes. The contract specifies that for a given request a certain response must be produced. It’s useful on the client-side to build stubs. But it’s even more important on the producer side. CDC framework generates code for an integration test. That test makes a request and verifies the producer, indeed, generated a valid response. If the response is different, it means the server does not support the given contract. And can’t be shipped. If it’s the same, we have a guarantee that the client will work. After all, the provider of the API can prove it supports whatever the client expects.
Let’s take a concrete example.
Imagine there’s an API that returns a movie’s director for a given title.
The consumer specifies the following contract:
“_When I make a RESTful GET request to
/movies/Titanic I expect JSON with
"director": "James Cameron".
When the client contacts the stub server generated by CDC framework, it’ll get the desired response.
On the other hand, the server must fulfil that contract.
So there’s a test generated on the server, based on that contract.
The test makes a request to
/movies/Titanic and verifies that the response has appropriate data.
The biggest advantage of CDC is the confidence that your client will work with a server. Moreover, the server can’t make a breaking change to the API. The generated tests will fail. If the client, on the other hand, needs some changes to the server’s API, a new contract is built.
The most popular implementations of CDC include PACT and Spring Cloud Contract. This approach is well established and can work with asynchronous APIs as well. An alternative to CDC is a strict schema that is driven by the producer.
That’s it, thanks for listening, bye!