PACT — Steering providers with provider state callbacks

Preeti Saini
TestVagrant
Published in
7 min readJun 22, 2020
source: https://google.com

As more and more microservices gets developed and deployed, the inter-communications between the microservices gets even more complex. Will not shy away to say these get into a very popular social status ‘complicated’. One change in any one’s status(version) and many broken hearts — only to be discovered in production.

Contract testing and one such tool PACT does provide a comprehensive solution to our ‘complicated’ status problem of microservices. Here are the quick bytes on contract testing and PACT -JVM implementation.

As we know pact consumers tests are pretty straight:

Step1: We set the expectations as part of pacts and run the unit tests against the pact mock provider.

Step2: Once the tests are passed, the pact file is generated at the consumer. We then publish the pact to let say Pact Broker, against the given consumer version and provider.

Step3: For the contract testing cycle to complete, we are required to verify these pact tests against the real provider.

and so the real challenges begin. And one such is — we do not have any control over some of the data, provider returns. This results in the failure of our pact tests at provider, which ideally should have passed, provided, the required data was already available with the provider.

To deep dive into the problem will be using the pact-JVM JUnit implementation.

Problem — Deep Dive

Scenario1: Let say I have a ‘user’ Rest API which returns the users details and addresses associated with each user. It also allows fetching the single user and its address details. So the endpoint to fetch a single user and its address, will look like ‘/api/users?userid=12345’ where ‘12345’ is nothing but the userid — a system-generated ID. Below is the snippet of how my consumer ‘user api’ pact for the single user now looks :

and the corresponding unit test will look like:

Now when we verify the provider against the pact file generated from above, don’t be surprised to know, it will fail with status code 404.

The very obvious reason is, the interaction tests at the provider will generate a random query parameter at run time (as per the given matchers), which anyways will not be known to the provider. If somehow we manage to get a valid userID and hardcode these in our consumer pact code -> is not a proper solution and will make our contract tests flaky with time.

Solving using Provider State Callbacks

Consumer Tests:

To address these and related concerns, Pact spec V3 generators introduced the concept of injecting values from provider state callbacks.

Provider States, essentially, define the state, the provider needs to be in, to be able to successfully verify a particular interaction from the pact file. Provider states as implemented as a callback gets executed before the interaction test, to set the desired state for a provider or gets executed just after the interaction test to tear-down the required provider state.

We can now also have values be injected from provider states into most places (paths, query parameters, headers, bodies, etc.) of interaction tests so that it can make a request/response that matches what the consumer expects.

Hence to solve the given problem, will now inject the `userid` value from the provider state callback. The updated query parameter will be:

The userid field in the response body>root> user and in address object, can also be updated to read the value returned by the provider state callback (Note: provider state callbacks return Map<String, Object> type, mapping the values to the requests/responses provided the Map key and expression used at consumer, matches):

As the underline expectation for this pact test is to have the given user available at provider, we can also have the user to be created, as part of the provider state set up method, be passed from the consumer to provider :

Note, the arguments passed as part of the .given should be, of type Map<String, Object>. As these arguments are also available with provider state tear-down callback, enables us to tear-down the data/state enabled in the setup method of the given interaction test at a provider.

The updated consumer user apipact for a single user is now :

And the corresponding unit test though remains the same:

A note of caution — Mock provider uses the ‘example’, provided as part of the ”queryParameterFromProviderState” method of a pact to generate the request/responses. Hence here have provided the same value ‘6789’ in query “/api/users?userId=6789” in my unit test. Otherwise, my consumer test would have failed with error:

Pact Test function failed with an exception, possibly due to Mismatches(mismatches=[PartialMismatch(mismatches=[QueryMismatch(queryParameter=userId, expected=6789, actual=1789’

Below is the snippet from pact *.json file, uploaded at the pact broker upon successful test run and on ./gradlew pactPublish. Do note the generators for “query”, “$.user.userId”, “$.user.address[*].userId” and “providerStates”

Provider Tests:

We can now write a provider verification test. It would be quite simple and will be like:

Note: Here I’m using the mock webserver to mock the provider responses but it’s better to use the real provider while verifying the contract at provider end.

Also, the test does:

  1. ‘@State’ uses the value — as provided by the consumer pact ‘.given’ description. This way the provider understands which callback method is to be executed before/after the interaction test.
  2. Method ‘toUserExistsState’ provider state callback, gets executed before the actual interaction from pact file runs. It receives the parameters from the provider state as passed by the consumer. We can use these parameters to create the desired data and return the Map as expected by request/response. Note → use the same ‘key’ as added in the regex expression in consumer pact, here I used ‘userID’. As I’m mocking the provider responses, I’m starting the mock server at ‘1090’, and enqueuing a mock response with a user, its address and returning the userID as Map<String, Object>.
  3. Method ‘tearDownTheCreatedData’ provider state callback, gets executed after the actual interaction from the pact file has run. It also receives the parameters from the provider state as passed by the consumer. If required, we can use these parameters to delete the user/data created as part of the setup method. As I am mocking the provider responses in my test, I’m just shutting down the mock server here.

The interaction test log snippet captured at provider :

Scenario 2: One other very possible use case will be the POST API which is dependent on a path parameter. For example — API adds a single address to the given user and returns and user and its addresses (updated) in the response. The API URI looks like “/api/users/<userId>/address”. From my verification test to pass at the provider, the provider expects `userid` to be available. Hence using the provider state callback and its value injection in the path, request body and response body, my pact and unit tests will be:

Note: In the above and earlier example at a consumer, have used lambda expressions inside the request/response body (which is of type LambdaDSLObject) — uses Lambda DSL for Pact, an updated DSL for consumer tests. This was introduced to overcome some of the known issues in the older DSL. For further details on LambdaDSL please visit Lambda DSL GitHub page. And yes, as it promises, it does make the request/response body better readable, easy to write and maintainable when working with complex JSON responses.

The Provider test will be :

Note: Here I’m using the mock webserver to mock the provider responses but it’s better to use the real provider while verifying the contract at provider end.

The interaction test log snippet captured at provider:

As part of the Pact Spec V3 generators, following DSL methods are allowed:

For JSON bodies, use valueFromProviderState.
For headers, use headerFromProviderState.
For query parameters, use queryParameterFromProviderState.
For paths, use pathFromProviderState.

Conclusion

Provider state callbacks and injecting values from the provider state setup method into the interaction tests (paths, query parameters, headers, bodies, etc.), results in better, reliable and more dynamic contract tests. These also enable the provider to meet the versatile data/state requirements of the interaction tests to generate request & response matching consumer expectations. Lastly, do try Lambda DSL in your consumer tests and you will see the benefits it brings in managing the request/response bodies, over the older pact DSL.

--

--