Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Example Consumer & Provider projects for Pact JVM

License

NotificationsYou must be signed in to change notification settings

Mikuu/Pact-JVM-Example

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.

Contents

1. Understand The Example Applications

Clone the codes to your local, then you can find:

1.1. Example Provider

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:

1.2. Example Consumer Miku

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.

1.3. Example Consumer Nanoha

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:

image

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.

2. Contract Test between Provider and Consumer Miku

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

2.1. Create Test Cases

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.

2.1.1. Basic Junit

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.

2.1.2. Junit Rule

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.

2.1.3. Junit DSL

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.

2.2. Run the Tests at Consumer Miku side

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.

2.3. Publish Pacts to Pact Broker

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 adocker-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.

image

Later, our Provider can fetch these Pacts from broker to drive the Contract Test.

2.4. Run the Contract Test at Provider side

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.

3. Gradle Configuration

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.

4. Contract Test between Provider and Consumer Nanoha

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.

4.1. Preparation for Provider State

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/pactStateChangewhich accept a POST request to set the.nationality value to be "Japan" (by default) or "null".

4.2. Create Test Case at Consumer Nanoha side

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.

4.3. Run Contract Test at Provider side

4.3.1. start Provider application

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

4.3.2. update Gradle configuration

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.

4.3.3. run the contract test

the same command as we did with Consumer Miku:

./gradlew :example-provider:pactVerify

Then you can get the test log in the same terminal.

5. Break Something

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.

5.1. Break something in Provider

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;    }}

5.2. Retest

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, +  }, }])...

6. Contribution

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.

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp