Like a lot of things in life, Consumer Driven Contract Testing is awesome until it isn't. And when testing with frameworks like Pact, things become tricky when you have to deal with dynamic or relative data.
I was working at a client were we had been using Pact extensively and everything was going well until the contact verification tests of one of the services started failing. That service had not changed in well over six to nine months. Turns out that particular service returned data for a given date relative to the current financial year. And we had just moved into a new financial year for that client.
Now you could argue that it was rather silly to create service that could only return data for the current financial year. But the financial year is a really important concept in the financial services sector (just ask any accountant or your financial team). In this case the service was returning data that is effected by member contributions to a super-annuation fund. For previous financial years, this is not needed because the financial year end processing would have calculated, locked down and published everything, but the position at any given date would not be known for only the current financial year.
So, a service that only accepts dates in the current financial year makes sense.
The problem with testing with a contract in time
Here the main issue was the consumer contract test had to choose a point in time that would be persisted to the contract (here a Pact file). This contract would have then been published, and it would not change again. Hence, that contract was only valid while that chosen point in time fell in the current financial year. Then a new contract would have to be generated with a valid date for each new financial year.
So the question arose: how can we create a contract for this service that is always valid for the current financial year?
The answer was simple: Use the date that the contract is verified on. That would only ever be in the current financial year.
So the next question arose: how can we use the date that the contract is verified on and not the one persisted with the contact?
That answer was not so simple.
Pact example generators
One way of solving this problem is to use the Generators feature of the V3 Pact specification. These are mechanisms in which values can be generated during the test execution, and we are going to use one that was recently added to Pact-JVM: the date and time generator.
In fact, solving this exact example led me to create the example generators for Pact in the first place.
The date and time generators allow you to generate relative date and time values at runtime based on an expression like now, today, tomorrow, now + 4 hours and next Monday at midnight. And today was all we needed to solve our current financial year problem.
To use the generators is easy. They are baked into the consumer DSLs. So, for instance, if you were using the Pact-JVM Java consumer DSL, you might have written the following snippet for the request body to the service in the consumer test:
.uponReceiving("A request for total member contributions to date")
.path("/contributions")
.method("POST")
.body(new PactDslJsonBody()
.numberType("memberNo", 1000)
.date("dateAt"))
.willRespondWith()
.status(200)
The date for the dateAt
field will be the current date when the consumer test is run.
You can now use the dateExpression
function to have an expression be evaluated at runtime when the provider is verified. So our snippet becomes:
.uponReceiving("A request for total member contributions to date")
.path("/contributions")
.method("POST")
.body(new PactDslJsonBody()
.numberType("memberNo", 1000)
.dateExpression("dateAt", "today"))
.willRespondWith()
.status(200)
The date value used will now be the current date both when the consumer test is run and when the provider is verified.
Yay, problem solved.