- Notifications
You must be signed in to change notification settings - Fork59
Example Consumer & Provider projects for Pact JVM
License
Mikuu/Pact-JVM-Example
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
These codes provide an example about how to do Contract Test with PACT JVM Junit,which uses Junit in Consumer side and Gradle task in Provider side, it will cover:
- Microservices examples created with Spring Boot.
- Example of one Provider to two Consumers.
- Write Consumer tests in different ways including using Basic Junit, Junit Rule and DSL method.
- Example of Provider State.
- Example of utilizing Pact Broker.
- PACT JVM Example
Clone the codes to your local, then you can find:
This is an API backend service which serves athttp://localhost:8080/information, consumerscan retrieve some person information by calling this endpoint with a query parametername,to start the provider:
./gradlew :example-provider:bootRun
then callhttp://localhost:8080/information?name=Miku will get:
and callhttp://localhost:8080/information?name=Nanoha will get:
This is the first example consumer we calledMiku, to start it:
./gradlew :example-consumer-miku:bootRun
then visithttp://localhost:8081/miku in your browser, you can get this:
compare with Provider's payload and the information on the web page, you can find that the attributessalary
andnationality
are not used by Miku.
This is the second example consumer we calledNanoha, to start it:
./gradlew :example-consumer-nanoha:bootRun
then visithttp://localhost:8082/nanoha in your browser, you can get this:
similar to Miku, Nanoha does not use the attributesalary
neither but uses attributenationality
, so thisis a little difference between the two consumers when consuming the response from the Provider's same endpoint.
Now, it's time to look into the tests.
This README will not go through all tests line by line, becausethe tests themselves are very simple and straightforward, so I will only point out some highlights foreach test. For detailed explanation about the codes, please refer theofficial document
By the time this example is created, PACT JVM Junit provides 3 ways to write the pact test fileat consumer side, theBasic Junit, theJunit Rule andJunit DSL.
PactBaseConsumerTest.java
@RunWith(SpringRunner.class)@SpringBootTestpublicclassPactBaseConsumerTestextendsConsumerPactTest {@AutowiredProviderServiceproviderService;@Override@Pact(provider="ExampleProvider",consumer="BaseConsumer")publicRequestResponsePactcreatePact(PactDslWithProviderbuilder) {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");returnbuilder .given("") .uponReceiving("Pact JVM example Pact interaction") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 45000,\n" +"\"name\":\"Hatsune Miku\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"hatsune.miku@ariman.com\",\n" +"\"Phone Number\":\"9090950\"\n" +" }\n" +"}") .toPact(); }@OverrideprotectedStringproviderName() {return"ExampleProvider"; }@OverrideprotectedStringconsumerName() {return"BaseConsumer"; }@OverrideprotectedvoidrunTest(MockServermockServer,PactTestExecutionContextcontext) {providerService.setBackendURL(mockServer.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku"); }}
TheproviderService
is the same one used in consumer Miku, we just use it to do a selfintegration test, the purpose for this is to check if consumer Miku can handle the mockedresponse correctly, then ensure the Pact content created are just as we need before we sendit to Provider.
mockServer.getUrl()
can return the mock server's url, which is to be used in our handler.
PactJunitRuleTest.java
@RunWith(SpringRunner.class)@SpringBootTestpublicclassPactJunitRuleTest {@AutowiredProviderServiceproviderService;@RulepublicPactProviderRulemockProvider =newPactProviderRule("ExampleProvider",this);@Pact(consumer ="JunitRuleConsumer")publicRequestResponsePactcreatePact(PactDslWithProviderbuilder) {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");returnbuilder .given("") .uponReceiving("Pact JVM example Pact interaction") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 45000,\n" +"\"name\":\"Hatsune Miku\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"hatsune.miku@ariman.com\",\n" +"\"Phone Number\":\"9090950\"\n" +" }\n" +"}") .toPact(); }@Test@PactVerificationpublicvoidrunTest() {providerService.setBackendURL(mockProvider.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku"); }}
This test uses Junit Rule which can simplify the writing of test cases comparing with the Basic Junit.
PactJunitRuleMultipleInteractionsTest.java
@RunWith(SpringRunner.class)@SpringBootTestpublicclassPactJunitRuleMultipleInteractionsTest {@AutowiredProviderServiceproviderService;@RulepublicPactProviderRulemockProvider =newPactProviderRule("ExampleProvider",this);@Pact(consumer="JunitRuleMultipleInteractionsConsumer")publicRequestResponsePactcreatePact(PactDslWithProviderbuilder) {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");returnbuilder .given("") .uponReceiving("Miku") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 45000,\n" +"\"name\":\"Hatsune Miku\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"hatsune.miku@ariman.com\",\n" +"\"Phone Number\":\"9090950\"\n" +" }\n" +"}") .given("") .uponReceiving("Nanoha") .path("/information") .query("name=Nanoha") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 80000,\n" +"\"name\":\"Takamachi Nanoha\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"takamachi.nanoha@ariman.com\",\n" +"\"Phone Number\":\"9090940\"\n" +" }\n" +"}") .toPact(); }@Test@PactVerification()publicvoidrunTest() {providerService.setBackendURL(mockProvider.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku");providerService.setBackendURL(mockProvider.getUrl(),"Nanoha");information =providerService.getInformation();assertEquals(information.getName(),"Takamachi Nanoha"); }}
This case uses Junit Rule too, but with two interactions in one Pact file.
PactJunitDSLTest
@RunWith(SpringRunner.class)@SpringBootTestpublicclassPactJunitDSLTest {@AutowiredProviderServiceproviderService;privatevoidcheckResult(PactVerificationResultresult) {if (resultinstanceofPactVerificationResult.Error) {thrownewRuntimeException(((PactVerificationResult.Error)result).getError()); }assertThat(result,is(instanceOf(PactVerificationResult.Ok.class))); }@TestpublicvoidtestPact1() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");RequestResponsePactpact =ConsumerPactBuilder .consumer("JunitDSLConsumer1") .hasPactWith("ExampleProvider") .given("") .uponReceiving("Query name is Miku") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 45000,\n" +"\"name\":\"Hatsune Miku\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"hatsune.miku@ariman.com\",\n" +"\"Phone Number\":\"9090950\"\n" +" }\n" +"}") .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault();PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl(),"Miku");Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku");returnnull; });checkResult(result); }@TestpublicvoidtestPact2() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");RequestResponsePactpact =ConsumerPactBuilder .consumer("JunitDSLConsumer2") .hasPactWith("ExampleProvider") .given("") .uponReceiving("Query name is Nanoha") .path("/information") .query("name=Nanoha") .method("GET") .willRespondWith() .headers(headers) .status(200) .body("{\n" +"\"salary\": 80000,\n" +"\"name\":\"Takamachi Nanoha\",\n" +"\"nationality\":\"Japan\",\n" +"\"contact\": {\n" +"\"Email\":\"takamachi.nanoha@ariman.com\",\n" +"\"Phone Number\":\"9090940\"\n" +" }\n" +"}") .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault();PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl(),"Nanoha");Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Takamachi Nanoha");returnnull; });checkResult(result); }}
Comparing with Basic Junit and Junit Rule usage, the DSL provides the ability to create multiple Pact filesin one test class.
PactJunitDSLJsonBodyTest
@RunWith(SpringRunner.class)@SpringBootTestpublicclassPactJunitDSLJsonBodyTest {@AutowiredProviderServiceproviderService;privatevoidcheckResult(PactVerificationResultresult) {if (resultinstanceofPactVerificationResult.Error) {thrownewRuntimeException(((PactVerificationResult.Error)result).getError()); }assertThat(result,is(instanceOf(PactVerificationResult.Ok.class))); }@TestpublicvoidtestWithPactDSLJsonBody() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");DslPartbody =newPactDslJsonBody() .numberType("salary",45000) .stringType("name","Hatsune Miku") .stringType("nationality","Japan") .object("contact") .stringValue("Email","hatsune.miku@ariman.com") .stringValue("Phone Number","9090950") .closeObject();RequestResponsePactpact =ConsumerPactBuilder .consumer("JunitDSLJsonBodyConsumer") .hasPactWith("ExampleProvider") .given("") .uponReceiving("Query name is Miku") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body(body) .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault(PactSpecVersion.V3);PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku");returnnull; });checkResult(result); }@TestpublicvoidtestWithLambdaDSLJsonBody() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");DslPartbody =newJsonBody((root) -> {root.numberValue("salary",45000);root.stringValue("name","Hatsune Miku");root.stringValue("nationality","Japan");root.object("contact", (contactObject) -> {contactObject.stringMatcher("Email",".*@ariman.com","hatsune.miku@ariman.com");contactObject.stringType("Phone Number","9090950"); }); }).build();RequestResponsePactpact =ConsumerPactBuilder .consumer("JunitDSLLambdaJsonBodyConsumer") .hasPactWith("ExampleProvider") .given("") .uponReceiving("Query name is Miku") .path("/information") .query("name=Miku") .method("GET") .willRespondWith() .headers(headers) .status(200) .body(body) .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault(PactSpecVersion.V3);PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Hatsune Miku");returnnull; });checkResult(result); }}
When use Json Body in DSL usage, we can control the test accuracy by defining whether we check the responsebody's attributes by Value or by Type, or even with Regular Expression. Comparing with normal Json body, theLambda statements can avoid using.close**()
methods to make the codes more clean. You can find moredescription about ithere.
Because we are using Junit, so to run the tests and create Pact files are very easy, just as what we alwaysrun our usual Unit Test:
./gradlew :example-consumer-miku:clean test
After that, you can find 7 JSON files created in folderPacts\Miku
. These are the Pacts which contain thecontract between Miku and Provider, and these Pacts will be used to drive the Contract Test later at Providerside.
The JSON files generated with tasktest
are in the local folder which are only reachable to our local Provider,while for real project practice, it's highly recommended to usePact Brokerto transport the Pacts between Consumers and Providers.
There's a
docker-compose.yml
file that aids setting up an instance of the Pact Broker.Rundocker-compose up
and you'll have a Broker running athttp://localhost/
.It'll set up an instance of PostgreSQL as well, but the data will be lost at every restart.In order to use it, inbuild.gradle
, setpactBrokerUrl
tohttp://localhost
and bothpactBrokerUsername
andpactBrokerPassword
to''
.
After you set up a Pact Broker server for your own, you can easily share your Pacts to the broker with thepactPublish
command:
./gradlew :example-consumer-miku:pactPublish
This command will upload your Pacts to the broker server:
> Task :example-consumer-miku:pactPublish Publishing JunitDSLConsumer1-ExampleProvider.json ... HTTP/1.1 200 OKPublishing JunitDSLJsonBodyConsumer-ExampleProvider.json ... HTTP/1.1 200 OKPublishing JunitDSLLambdaJsonBodyConsumer-ExampleProvider.json ... HTTP/1.1 200 OKPublishing BaseConsumer-ExampleProvider.json ... HTTP/1.1 200 OKPublishing JunitRuleConsumer-ExampleProvider.json ... HTTP/1.1 200 OKPublishing JunitRuleMultipleInteractionsConsumer-ExampleProvider.json ... HTTP/1.1 200 OKPublishing JunitDSLConsumer2-ExampleProvider.json ... HTTP/1.1 200 OK
Then you can find theRelationships inour Pact Broker
you can find their are '7' consumers to 1 provider, in real project, it should NOT like that, because we have only oneconsumer Miku here, it should be only 1 consumer to 1 provider, while in this example, making it's '7' is only to showthat how Pact Broker can display the relationships beautifully.
Later, our Provider can fetch these Pacts from broker to drive the Contract Test.
We are using the Pact Gradle task in Provider side to run the Contract Test, which can be very easy withoutwriting any code, just execute thepactVerify
task.Before we run the test, make sure the Provider API is already started and running at our localhost.
Then we can run the test as:
./gradlew :example-provider:pactVerify
and you can find the test results at the command line, would be something likes:
Arimans-MacBook-Pro:pact-jvm-example ariman$ ./gradlew :example-provider:pactVerify > Task :example-provider:pactVerify_ExampleProvider Verifying a pact between Miku - Base contract and ExampleProvider [Using File /Users/ariman/Workspace/Pacting/pact-jvm-example/Pacts/Miku/BaseConsumer-ExampleProvider.json] Given WARNING: State Change ignored as there is no stateChange URL Consumer Miku returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json;charset=UTF-8" (OK) has a matching body (OK) Given WARNING: State Change ignored as there is no stateChange URL Pact JVM example Pact interaction returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json;charset=UTF-8" (OK) has a matching body (OK) ... Verifying a pact between JunitRuleMultipleInteractionsConsumer and ExampleProvider [from Pact Broker https://ariman.pact.dius.com.au/pacts/provider/ExampleProvider/consumer/JunitRuleMultipleInteractionsConsumer/version/1.0.0] Given WARNING: State Change ignored as there is no stateChange URL Miku returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json;charset=UTF-8" (OK) has a matching body (OK) Given WARNING: State Change ignored as there is no stateChange URL Nanoha returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json;charset=UTF-8" (OK) has a matching body (OK)
You can find the tests checked the Pacts from both local and remote Pact Broker.
Since this example is open to all Pact learners, means everyone can upload their own Pacts to this Pact Broker, includingcorrect and incorrect pacts, so don't be surprised if your tests run failed with the pacts from this Pact Broker. You canalways upload correct pacts to the broker, but just don't rely on it, the pacts in this broker may be cleared at anytimefor reset a clean environment.
Before we continue to Consumer Nanoha, let's look at the Gradle Configuration first:
project(':example-consumer-miku') {... test { systemProperties['pact.rootDir']="$rootDir/Pacts/Miku" } pact { publish { pactDirectory="$rootDir/Pacts/Miku" pactBrokerUrl= mybrokerUrl pactBrokerUsername= mybrokerUser pactBrokerPassword= mybrokerPassword } }...}project(':example-consumer-nanoha') {... test { systemProperties['pact.rootDir']="$rootDir/Pacts/Nanoha" }...}importjava.net.URLproject(':example-provider') {... pact { serviceProviders {ExampleProvider { protocol='http' host='localhost' port=8080 path='/'// Test Pacts from local Miku hasPactWith('Miku - Base contract') { pactSource= file("$rootDir/Pacts/Miku/BaseConsumer-ExampleProvider.json") } hasPactsWith('Miku - All contracts') { pactFileLocation= file("$rootDir/Pacts/Miku") }// Test Pacts from Pact Broker hasPactsFromPactBroker(mybrokerUrl,authentication: ['Basic', mybrokerUser, mybrokerPassword])// Test Pacts from local Nanoha// hasPactWith('Nanoha - With Nantionality') {// pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json")// }// hasPactWith('Nanoha - No Nantionality') {// stateChangeUrl = new URL('http://localhost:8080/pactStateChange')// pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json")// } } } } }
Here we have configuration separately for Consumer Nanoha, Consumer Miku and Provider.
systemProperties['pact.rootDir']
defines the path where consumer Miku and Nanoha generate their Pacts files locally.pact { ... }
defines the the Pact Broker URL and where to fetch pacts to upload to the broker.Then in Provider configuration,
hasPactWith()
andhasPactsWith()
specify the local path to find Pact files,andhasPactsFromPactBroker
specify the remote Pact Broker to fetch Pact files.
Why comment Nanoha's Pacts path? Because we haven't created Nanoha's Pacts, so it will raise exception withthat configuration, we can uncomment that path later after we created the Nanoha's Pacts files.
The Contract Test between Nanoha and Provider is similar to Miku, the difference I want to demonstrate here isProvider State, you can find more detailed explanation about the Provider Statehere.
In our example Provider, the returned payload for both Miku and Nanoha have a property.nationality
, this kindof properties values, in real project, could always be queried from database or dependency service, but in thisexample, to simplify the Provider application logic, I just create a static property to simulate the data storage:
provider.ulti.Nationality
publicclassNationality {privatestaticStringnationality ="Japan";publicstaticStringgetNationality() {returnnationality; }publicstaticvoidsetNationality(Stringnationality) {Nationality.nationality =nationality; }}
Then we can change the.nationality
value at anytime to achieve our test purpose. To change its value, I createdaPactController
which can accept and reset the state value by sending a POST to endpoint/pactStateChange
:
provider.PactController
@Profile("pact")@RestControllerpublicclassPactController {@RequestMapping(value ="/pactStateChange",method =RequestMethod.POST)publicPactStateChangeResponseDTOproviderState(@RequestBodyPactStatebody) {switch (body.getState()) {case"No nationality":Nationality.setNationality(null);System.out.println("Pact State Change >> remove nationality ...");break;case"Default nationality":Nationality.setNationality("Japan");System.out.println("Pact Sate Change >> set default nationality ...");break; }// This response is not mandatory for Pact state change. The only reason is the current Pact-JVM v4.0.4 does// check the stateChange request's response, more exactly, checking the response's Content-Type, couldn't be// null, so it MUST return something here.PactStateChangeResponseDTOpactStateChangeResponse =newPactStateChangeResponseDTO();pactStateChangeResponse.setState(body.getState());returnpactStateChangeResponse; }}
Because this controller should only be available atNon-Production environment, so I added a Profile annotationto limit that this endpoint is only existing when Provider application is running with a "pact" profile, in anotherwords, it only works in test environment.
So, to summary all above things, when Provider is running with a "pact" profile, it serves an endpoint/pactStateChange
which accept a POST request to set the.nationality
value to be "Japan" (by default) or "null".
The test cases at Nanoha side is using DSL Lambda, here we have two cases in our test, the main difference betweenthem is the expectation for.nationality
and the Provider State we set in.given()
.
@RunWith(SpringRunner.class)@SpringBootTestpublicclassNationalityPactTest {@AutowiredProviderServiceproviderService;privatevoidcheckResult(PactVerificationResultresult) {if (resultinstanceofPactVerificationResult.Error) {thrownewRuntimeException(((PactVerificationResult.Error)result).getError()); }assertThat(result,is(instanceOf(PactVerificationResult.Ok.class))); }@TestpublicvoidtestWithNationality() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");DslPartbody =newJsonBody((root) -> {root.numberType("salary");root.stringValue("name","Takamachi Nanoha");root.stringValue("nationality","Japan");root.object("contact", (contactObject) -> {contactObject.stringMatcher("Email",".*@ariman.com","takamachi.nanoha@ariman.com");contactObject.stringType("Phone Number","9090940"); }); }).build();RequestResponsePactpact =ConsumerPactBuilder .consumer("ConsumerNanohaWithNationality") .hasPactWith("ExampleProvider") .given("") .uponReceiving("Query name is Nanoha") .path("/information") .query("name=Nanoha") .method("GET") .willRespondWith() .headers(headers) .status(200) .body(body) .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault(PactSpecVersion.V3);PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Takamachi Nanoha");assertEquals(information.getNationality(),"Japan");returnnull; });checkResult(result); }@TestpublicvoidtestNoNationality() {Map<String,String>headers =newHashMap<String,String>();headers.put("Content-Type","application/json;charset=UTF-8");DslPartbody =newJsonBody((root) -> {root.numberType("salary");root.stringValue("name","Takamachi Nanoha");root.stringValue("nationality",null);root.object("contact", (contactObject) -> {contactObject.stringMatcher("Email",".*@ariman.com","takamachi.nanoha@ariman.com");contactObject.stringType("Phone Number","9090940"); }); }).build();RequestResponsePactpact =ConsumerPactBuilder .consumer("ConsumerNanohaNoNationality") .hasPactWith("ExampleProvider") .given("No nationality") .uponReceiving("Query name is Nanoha") .path("/information") .query("name=Nanoha") .method("GET") .willRespondWith() .headers(headers) .status(200) .body(body) .toPact();MockProviderConfigconfig =MockProviderConfig.createDefault(PactSpecVersion.V3);PactVerificationResultresult =runConsumerTest(pact,config, (mockServer,context) -> {providerService.setBackendURL(mockServer.getUrl());Informationinformation =providerService.getInformation();assertEquals(information.getName(),"Takamachi Nanoha");assertNull(information.getNationality());returnnull; });checkResult(result); }}
To run the test:
./gradlew :example-consumer-nanoha:clean test
Then you can find two Pacts files generated atPacts\Nanoha
.
As we mentioned above, before we running our test, we need start Provider application with profile "pact" (if yourProvider application is already started when you ran test with Miku, please kill it first), the commandis:
SPRING_PROFILES_ACTIVE=pact ./gradlew :example-provider:bootRun
it's time to uncomment the Pacts file searching path in Provider configuration:
build.gralde
hasPactWith('Nanoha - With Nantionality') { pactSource= file("$rootDir/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json") } hasPactWith('Nanoha - No Nantionality') { stateChangeUrl=newURL('http://localhost:8080/pactStateChange') pactSource= file("$rootDir/Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json") }
The first Pact will check the payload with default.nationality
which value isJapan
, while the second Pact, wedefine astateChangeUrl
which is implemented byPactController
in Provider as we talked above, and the POST bodyis defined in.given()
in the Pact.
the same command as we did with Consumer Miku:
./gradlew :example-provider:pactVerify
Then you can get the test log in the same terminal.
Is everything done? Yes, if you followed all above introductions until here, you should be able to create your ContractTest by yourself. But before you go to real, let's try to break something to see how Pact could find the defect.
Both Miku and Nanoha consume Provider's response for a property.name
, in real project, there might be a case thatProvider would like to change this property name to.fullname
, this could be a classic Contract Breaking which wouldbe captured by Pact.
To do that, we need modify several lines of codes in Provider which could be a little difficult tounderstand, especially for a some testers who are not familiar to Spring Boot application.
So to make the breaking simple, let's just force Provider returnsnull
as the value to.name
to all consumers, this can be easily done byadding only a single line:
provider.InformationController
@RestControllerpublicclassInformationController { ...information.setName(null);returninformation; }}
Restart your Provider application and run the test again, you can get the test failures as this:
...Verifying a pact between Nanoha - With Nantionality and ExampleProvider [Using File /Users/Biao/Workspace/Pacting/Pact-JVM-Example/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json] Given WARNING: State Change ignored as there is no stateChange URL Query name is Nanoha returns a response which has status code 200 (OK) has a matching body (FAILED)Failures:0) Verifying a pact between Nanoha - With Nantionality and ExampleProvider - Query name is Nanoha Given returns a response which has a matching body Verifying a pact between Nanoha - With Nantionality and ExampleProvider - Query name is Nanoha Given returns a response which has a matching body=BodyComparisonResult(mismatches={$.nationality=[BodyMismatch(expected="Japan", actual=null, mismatch=Expected "Japan" but received null, path=$.nationality, diff=null)], $.name=[BodyMismatch(expected="Takamachi Nanoha", actual=null, mismatch=Expected "Takamachi Nanoha" but received null, path=$.name, diff=null)]}, diff=[{, - "nationality": "Japan",, + "salary": 80000,, + "name": null,, + "nationality": null,, "contact": {, "Phone Number": "9090940", - },, - "name": "Takamachi Nanoha",, - "salary": 294875537, + }, }])...
I'm not always tracking this example repository with the latest Pact-JVM version, so if you find there is new Pact-JVM released, and you'd like to put itinto this example, any PR is welcome.