This Showcase demonstrates how Spring Cloud Contract (SCC) can be used to decouple consumer - provider relationships during testing.
Most of the manageable / testable risk in consumer to provider (e.g. service to service, frontend to backend etc.) communication over HTTP or via a messaging framework is semantic in nature. No amount of testing will guarantee that the provider will not exhibit unexpected behaviour at runtime. What can actually be tested in a reliable and reproducible way is the semantics of the communication - basically "do both parties speak the same language?". This is where (consumer-driven) contracts and SCC come into play.
With most HTTP-based APIs the consumer - provider relationship is based on a request - response model:
Each request with its corresponding response can be viewed as a single interaction. Each kind of interaction with all its relevant permutations is the basis for the tests that need to be executed when validating functionality.
The same holds true for asynchronous communication via messaging. It is irrelevant that message-based interactions lack the response aspect, or that in theory days can pass until a consumer picks up a message that has been sent by a provider. The only thing that matters (in the scope of contract testing) is that the message can be understood.
In general there are two kinds of automated tests that are written on the consumer-side for validating interactions with a given provider:
Tests with Service-Simulation:
These tests are based on the consumer’s assumptions, generally derived from some kind of API documentation, about the provider’s behaviour and request - response data model. Since assumptions might be wrong or outdated, relying on these kinds of tests as the only kind of risk management is a risk in and of itself.
Integration Tests in a Staging Environment
These tests are not based on anyone’s assumptions. Instead, the "real" provider is used by the "real" consumer in some kind of staging environment. On the one hand, these kinds of tests are generally the closest we can come to verifying the "real" behaviour of the consumer - provider relationship. On the other hand, maintaining test data, current versions of the provider, configuration, etc. of these staging environments requires an enormous amount of time and money for a comparably small return on investment.
Combining both approaches by moving most testing effort from full integration tests on a staging environment to the much faster and cheaper service-simulator approach is generally a good idea. As an example, the integration test could be limited to one or two happy path min/max cases while most of the error handling is done using the simulation approach. Without some way of validating the consumer’s assumptions against the actual provider, there will always be unmanaged risk and a need for some kind of integration testing.
The following describes only one of the possible workflows when using Spring Cloud Contract. For an overview of other possible flows check out the Working with Spring Cloud Contract part of the official documentation.
What if consumers could communicate their assumptions to providers and get validated service simulations in return?
That’s exactly what Spring Cloud Contract is doing:
Consumers define contracts using a number of different formats (e.g. Kotlin Script, Groovy, YAML and even PACT JSONs). From these contracts the Provider will generate tests to be executing during its continuous integration builds. As a result of those builds, an artifact is produced which contains stubs / simulations for each known contract. This artifact is then pushed to a common repository. From there the consumer can download and use the now verified stubs in its own tests.
Most provider APIs will have more than one consumer.
With multiple consumers providing contracts to the same provider, another - more social - benefit comes into play: The provider now knows about each and every consumer of its API and has knowledge about the data that is actually consumed by someone. This makes it possible to change the API and know exactly which consumer will be negatively affected by the change. The provider can then talk to the team responsible for the affected consumer and plan the change in a much more effective way than having to start a global deprecation process or whatever other mechanism is used for implementing changes.
-
Cheap and fast tests on the consumer-side.
-
Cheap and fast tests on the provider-side.
-
Involve the provider in the testing efforts without needing to rely on a deployed environment.
-
Give the provider an overview of its consumers.
-
Open communication channels between consumer and provider teams.
-
Focus the needed communication of the provider team on those consumers that actually need to be talked to. (e.g. when upcoming changes to the API would break one or more contracts)
Contains two modules:
provider: A simple "Library Service" (as in managing a collection of books). It demonstrates the provider-side JUnit 5 and Spring Boot integration.
consumer-one: A consumer of the Library Service interested in a book’s isbn
, title
and authors
attributes.
It demonstrates a simple consumer-side JUnit 5 integration without anything too special.
Except a slightly more complex response expectation.
consumer-two: A consumer of the Library Service interested in a book’s isbn
, title
and numberOfPages
attributes.
It demonstrates a slightly more complex consumer-side contract using parameterized provider-state expectations.
None of the consumers is interested in a book’s description
attribute.
To demonstrate the advantages contract testing provides, the description
can be deleted from the provider’s Book
definition without any of the consumers suffering any consequences.
If any of the other attributes is changed at least one, if not both, of the contracts will break.
provider: A simple "Library Service" that notifies consumers about the creation of new books by pushing notifications to a message broker.
consumer: A consumer of the Library Service that reads the messages sent by the provider to add the books to its own library.
The provider and consumer communicate through a Kafka broker instance and implement their contract tests with the help of Spring’s
EmbeddedKafka
feature.