I don't understand the use of assert in @PactVerification
. To me it seams more like a complicated way of saying 1 == 1
. For example:
import static org.assertj.core.api.Assertions.assertThat;
public class PactConsumerDrivenContractUnitTest {
@Rule
public PactProviderRuleMk2 mockProvider
= new PactProviderRuleMk2("test_provider", "localhost", 8080, this);
@Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body("{\"condition\": true, \"name\": \"tom\"}")
}
@Test
@PactVerification()
public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() {
//when
ResponseEntity<String> response
= new RestTemplate().getForEntity(mockProvider.getUrl(), String.class);
//then
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
}
}
So first in "createPact" we state
body("{\"condition\": true, \"name\": \"tom\"}")
Then in givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody
annotated @PactVerification
we do this
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
But why? We just said that! As far as I can see the assertion does not show up in the generated Pact file. It seams to fill no purpose?
In addition to that, I thought that the idea of contract testing was to reduce the need for integration test since they can break for example if test data changes. But here we still depend on test data. If there are no "Tom" in the Provider, then the test will fail. I primarily wanted to test if the contract is broken, not if the test data has changed.
The example given is a contrived one. In real life using Pact, you wouldn't do this. Your PactVerification
would invoke a collaboration method/class/thing which is responsible for the external call to the service you are mocking.
So your assertions are then on what the collaborating function is doing.
Eg. A User Service might create an object with certain properties, that you know only are populated by that external call.
Testing assertions in your @PactVerification
test method is not mandatory, yet still it might be very helpful. E.g. you may make a typo in your JSON body string and you wont be able to catch it in your test and it will break provider's pipeline. Assertions in this case have nothing to do with generated Pact file, they play role of a guard that checks in the end if the contract you have just defined (RequestResponsePact
) matches all your expectations (assertions
).
Also it is worth mentioning that your consumer contract tests should break only if provider tries to release a change that makes your expectations broken. And this is consumer's responsibility to write good contract tests. In your example you have defined following expectation:
@Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body("{\"condition\": true, \"name\": \"tom\"}")
}
This contract will be satisfied as long as condition
== true
and name
== tom
. This is over-specification of a response. You could define more flexible response with PactDslJsonBody DSL instead:
@Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
final DslPart body = new PactDslJsonBody()
.stringType("name", "tom")
.booleanType("condition", true);
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body(body)
.toPact();
}
This fragment will generate Pact file like:
{
"provider": {
"name": "providerA"
},
"consumer": {
"name": "test_consumer"
},
"interactions": [
{
"description": "GET REQUEST",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json; charset=UTF-8"
},
"body": {
"condition": true,
"name": "tom"
},
"matchingRules": {
"body": {
"$.name": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.condition": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "test GET "
}
]
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.10"
}
}
}
The main difference is that this Pact file uses matchingRules
to test if:
condition
field is boolean
name
field is String
For strings you can also use PactDslJsonBody.stringMatcher(name, regex, value)
method if needed. It allows you to define regular expression that will be tested using current field value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With