├── .gitignore ├── LICENSE ├── Pacts ├── Miku │ └── BaseConsumer-ExampleProvider.json └── Nanoha │ └── ConsumerNanohaNoNationality-ExampleProvider.json ├── README.MD ├── build.gradle ├── docker-compose.yml ├── example-consumer-miku ├── application.properties ├── gradle.properties ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── ariman │ │ │ └── pact │ │ │ └── consumer │ │ │ ├── Application.java │ │ │ ├── Information.java │ │ │ ├── InformationController.java │ │ │ └── ProviderService.java │ └── resources │ │ ├── static │ │ ├── css │ │ │ └── cover.css │ │ └── img │ │ │ └── miku.png │ │ └── templates │ │ └── miku.html │ └── test │ └── java │ └── ariman │ └── pact │ └── consumer │ ├── PactBaseConsumerTest.java │ ├── PactJunitDSLJsonBodyTest.java │ ├── PactJunitDSLTest.java │ ├── PactJunitRuleMultipleInteractionsTest.java │ └── PactJunitRuleTest.java ├── example-consumer-nanoha ├── application.properties ├── gradle.properties ├── settings.gradle ├── src │ ├── main │ │ ├── java │ │ │ └── ariman │ │ │ │ └── pact │ │ │ │ └── consumer │ │ │ │ ├── Application.java │ │ │ │ ├── Information.java │ │ │ │ ├── InformationController.java │ │ │ │ └── ProviderService.java │ │ └── resources │ │ │ ├── static │ │ │ ├── css │ │ │ │ └── cover.css │ │ │ └── img │ │ │ │ └── nanoha.png │ │ │ └── templates │ │ │ └── nanoha.html │ └── test │ │ └── java │ │ └── ariman │ │ └── pact │ │ └── consumer │ │ └── NationalityPactTest.java └── target │ └── pacts │ ├── BaseConsumer-ExampleProvider.json │ ├── JunitDSLConsumer1-ExampleProvider.json │ ├── JunitDSLConsumer2-ExampleProvider.json │ ├── JunitDSLJsonBodyConsumer-ExampleProvider.json │ ├── JunitDSLLambdaJsonBodyConsumer-ExampleProvider.json │ ├── JunitRuleConsumer-ExampleProvider.json │ └── JunitRuleMultipleInteractionsConsumer-ExampleProvider.json ├── example-provider ├── application.yml ├── settings.gradle └── src │ └── main │ └── java │ └── provider │ ├── Application.java │ ├── Information.java │ ├── InformationController.java │ ├── PactController.java │ ├── PactState.java │ ├── PactStateChangeResponseDTO.java │ └── ulti │ └── Nationality.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── consumer.miku.png ├── consumer.nanoha.png ├── pact-broker.png ├── provider.miku.png └── provider.nanoha.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .idea/ 3 | .gradle/ 4 | *.iml 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mikuu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pacts/Miku/BaseConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "BaseConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Pact JVM example Pact interaction", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset\u003dUTF-8" 24 | }, 25 | "body": { 26 | "salary": 45000, 27 | "name": "Hatsune Miku", 28 | "nationality": "Japan", 29 | "contact": { 30 | "Email": "hatsune.miku@ariman.com", 31 | "Phone Number": "9090950" 32 | } 33 | } 34 | }, 35 | "providerStates": [ 36 | { 37 | "name": "" 38 | } 39 | ] 40 | } 41 | ], 42 | "metadata": { 43 | "pactSpecification": { 44 | "version": "3.0.0" 45 | }, 46 | "pact-jvm": { 47 | "version": "4.0.3" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "ConsumerNanohaNoNationality" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Query name is Nanoha", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Nanoha" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset\u003dUTF-8" 24 | }, 25 | "body": { 26 | "nationality": null, 27 | "contact": { 28 | "Email": "takamachi.nanoha@ariman.com", 29 | "Phone Number": "9090940" 30 | }, 31 | "name": "Takamachi Nanoha", 32 | "salary": 100 33 | }, 34 | "matchingRules": { 35 | "body": { 36 | "$.salary": { 37 | "matchers": [ 38 | { 39 | "match": "number" 40 | } 41 | ], 42 | "combine": "AND" 43 | }, 44 | "$.contact.Email": { 45 | "matchers": [ 46 | { 47 | "match": "regex", 48 | "regex": ".*@ariman.com" 49 | } 50 | ], 51 | "combine": "AND" 52 | }, 53 | "$.contact[\u0027Phone Number\u0027]": { 54 | "matchers": [ 55 | { 56 | "match": "type" 57 | } 58 | ], 59 | "combine": "AND" 60 | } 61 | } 62 | }, 63 | "generators": { 64 | "body": { 65 | "$.salary": { 66 | "type": "RandomInt", 67 | "min": 0, 68 | "max": 2147483647 69 | } 70 | } 71 | } 72 | }, 73 | "providerStates": [ 74 | { 75 | "name": "No nationality" 76 | } 77 | ] 78 | } 79 | ], 80 | "metadata": { 81 | "pactSpecification": { 82 | "version": "3.0.0" 83 | }, 84 | "pact-jvm": { 85 | "version": "4.0.3" 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # PACT JVM Example 2 | 3 | These codes provide an example about how to do Contract Test with PACT JVM Junit, 4 | which uses Junit in Consumer side and Gradle task in Provider side, it will cover: 5 | 6 | - Microservices examples created with Spring Boot. 7 | - Example of one Provider to two Consumers. 8 | - Write Consumer tests in different ways including using Basic Junit, Junit Rule and DSL method. 9 | - Example of Provider State. 10 | - Example of utilizing Pact Broker. 11 | 12 | 13 | Contents 14 | ======== 15 | - [PACT JVM Example](#pact-jvm-example) 16 | * [1. Understand The Example Applications](#1-understand-the-example-applications) 17 | + [1.1. Example Provider](#11-example-provider) 18 | + [1.2. Example Consumer Miku](#12-example-consumer-miku) 19 | + [1.3. Example Consumer Nanoha](#13-example-consumer-nanoha) 20 | * [2. Contract Test between Provider and Consumer Miku](#2-contract-test-between-provider-and-consumer-miku) 21 | + [2.1. Create Test Cases](#21-create-test-cases) 22 | - [2.1.1. Basic Junit](#211-basic-junit) 23 | - [2.1.2. Junit Rule](#212-junit-rule) 24 | - [2.1.3. Junit DSL](#213-junit-dsl) 25 | + [2.2. Run the Tests at Consumer Miku side](#22-run-the-tests-at-consumer-miku-side) 26 | + [2.3. Publish Pacts to Pact Broker](#23-publish-pacts-to-pact-broker) 27 | + [2.4. Run the Contract Test at Provider side](#24-run-the-contract-test-at-provider-side) 28 | * [3. Gradle Configuration](#3-gradle-configuration) 29 | * [4. Contract Test between Provider and Consumer Nanoha](#4-contract-test-between-provider-and-consumer-nanoha) 30 | + [4.1. Preparation for Provider State](#41-preparation-for-provider-state) 31 | + [4.2. Create Test Case at Consumer Nanoha side](#42-create-test-case-at-consumer-nanoha-side) 32 | + [4.3. Run Contract Test at Provider side](#43-run-contract-test-at-provider-side) 33 | - [4.3.1. start Provider application](#431-start-provider-application) 34 | - [4.3.2. update Gradle configuration](#432-update-gradle-configuration) 35 | - [4.3.3. run the contract test](#433-run-the-contract-test) 36 | * [5. Break Something](#5-break-something) 37 | + [5.1. Break something in Provider](#51-break-something-in-provider) 38 | + [5.2. Retest](#52-retest) 39 | * [6. Contribution](#6-contribution) 40 | 41 | 42 | ## 1. Understand The Example Applications 43 | Clone the codes to your local, then you can find: 44 | 45 | ### 1.1. Example Provider 46 | This is an API backend service which serves at http://localhost:8080/information, consumers 47 | can retrieve some person information by calling this endpoint with a query parameter **name**, 48 | to start the provider: 49 | 50 | `./gradlew :example-provider:bootRun` 51 | 52 | then call http://localhost:8080/information?name=Miku will get: 53 | 54 | ![](https://github.com/Mikuu/Pact-JVM-Example/blob/master/screenshot/provider.miku.png) 55 | 56 | and call http://localhost:8080/information?name=Nanoha will get: 57 | 58 | ![](https://github.com/Mikuu/Pact-JVM-Example/blob/master/screenshot/provider.nanoha.png) 59 | 60 | ### 1.2. Example Consumer Miku 61 | This is the first example consumer we called [Miku](https://en.wikipedia.org/wiki/Hatsune_Miku), to start it: 62 | 63 | `./gradlew :example-consumer-miku:bootRun` 64 | 65 | then visit http://localhost:8081/miku in your browser, you can get this: 66 | 67 | ![](https://github.com/Mikuu/Pact-JVM-Example/blob/master/screenshot/consumer.miku.png) 68 | 69 | compare with Provider's payload and the information on the web page, you can find that the attributes `salary` and 70 | `nationality` are not used by Miku. 71 | 72 | ### 1.3. Example Consumer Nanoha 73 | This is the second example consumer we called [Nanoha](http://nanoha.wikia.com/wiki/Nanoha_Takamachi), to start it: 74 | 75 | `./gradlew :example-consumer-nanoha:bootRun` 76 | 77 | then visit http://localhost:8082/nanoha in your browser, you can get this: 78 | 79 | ![image](https://github.com/Mikuu/Pact-JVM-Example/blob/master/screenshot/consumer.nanoha.png) 80 | 81 | similar to Miku, Nanoha does not use the attribute `salary` neither but uses attribute `nationality`, so this 82 | is a little difference between the two consumers when consuming the response from the Provider's same endpoint. 83 | 84 | 85 | ## 2. Contract Test between Provider and Consumer Miku 86 | 87 | Now, it's time to look into the tests. 88 | 89 | > This README will not go through all tests line by line, because 90 | the tests themselves are very simple and straightforward, so I will only point out some highlights for 91 | each test. For detailed explanation about the codes, please refer the [official document](https://github.com/DiUS/pact-jvm/tree/be4a32b08ebbd89321dc37cbfe838fdac37774b3/pact-jvm-consumer-junit) 92 | 93 | ### 2.1. Create Test Cases 94 | 95 | By the time this example is created, PACT JVM Junit provides 3 ways to write the pact test file 96 | at consumer side, the **Basic Junit**, the **Junit Rule** and **Junit DSL**. 97 | 98 | 99 | #### 2.1.1. Basic Junit 100 | `PactBaseConsumerTest.java` 101 | ```java 102 | @RunWith(SpringRunner.class) 103 | @SpringBootTest 104 | public class PactBaseConsumerTest extends ConsumerPactTest { 105 | 106 | @Autowired 107 | ProviderService providerService; 108 | 109 | @Override 110 | @Pact(provider="ExampleProvider", consumer="BaseConsumer") 111 | public RequestResponsePact createPact(PactDslWithProvider builder) { 112 | Map headers = new HashMap(); 113 | headers.put("Content-Type", "application/json;charset=UTF-8"); 114 | 115 | return builder 116 | .given("") 117 | .uponReceiving("Pact JVM example Pact interaction") 118 | .path("/information") 119 | .query("name=Miku") 120 | .method("GET") 121 | .willRespondWith() 122 | .headers(headers) 123 | .status(200) 124 | .body("{\n" + 125 | " \"salary\": 45000,\n" + 126 | " \"name\": \"Hatsune Miku\",\n" + 127 | " \"nationality\": \"Japan\",\n" + 128 | " \"contact\": {\n" + 129 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 130 | " \"Phone Number\": \"9090950\"\n" + 131 | " }\n" + 132 | "}") 133 | 134 | .toPact(); 135 | } 136 | 137 | @Override 138 | protected String providerName() { 139 | return "ExampleProvider"; 140 | } 141 | 142 | @Override 143 | protected String consumerName() { 144 | return "BaseConsumer"; 145 | } 146 | 147 | @Override 148 | protected void runTest(MockServer mockServer, PactTestExecutionContext context) { 149 | providerService.setBackendURL(mockServer.getUrl()); 150 | Information information = providerService.getInformation(); 151 | assertEquals(information.getName(), "Hatsune Miku"); 152 | } 153 | } 154 | ``` 155 | The `providerService` is the same one used in consumer Miku, we just use it to do a self 156 | integration test, the purpose for this is to check if consumer Miku can handle the mocked 157 | response correctly, then ensure the Pact content created are just as we need before we send 158 | it to Provider. 159 | 160 | `mockServer.getUrl()` can return the mock server's url, which is to be used in our handler. 161 | 162 | #### 2.1.2. Junit Rule 163 | `PactJunitRuleTest.java` 164 | ```java 165 | @RunWith(SpringRunner.class) 166 | @SpringBootTest 167 | public class PactJunitRuleTest { 168 | 169 | @Autowired 170 | ProviderService providerService; 171 | 172 | @Rule 173 | public PactProviderRule mockProvider = new PactProviderRule("ExampleProvider", this); 174 | 175 | @Pact(consumer = "JunitRuleConsumer") 176 | public RequestResponsePact createPact(PactDslWithProvider builder) { 177 | Map headers = new HashMap(); 178 | headers.put("Content-Type", "application/json;charset=UTF-8"); 179 | 180 | return builder 181 | .given("") 182 | .uponReceiving("Pact JVM example Pact interaction") 183 | .path("/information") 184 | .query("name=Miku") 185 | .method("GET") 186 | .willRespondWith() 187 | .headers(headers) 188 | .status(200) 189 | .body("{\n" + 190 | " \"salary\": 45000,\n" + 191 | " \"name\": \"Hatsune Miku\",\n" + 192 | " \"nationality\": \"Japan\",\n" + 193 | " \"contact\": {\n" + 194 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 195 | " \"Phone Number\": \"9090950\"\n" + 196 | " }\n" + 197 | "}") 198 | .toPact(); 199 | } 200 | 201 | @Test 202 | @PactVerification 203 | public void runTest() { 204 | providerService.setBackendURL(mockProvider.getUrl()); 205 | Information information = providerService.getInformation(); 206 | assertEquals(information.getName(), "Hatsune Miku"); 207 | } 208 | } 209 | ``` 210 | This test uses Junit Rule which can simplify the writing of test cases comparing with the Basic Junit. 211 | 212 | `PactJunitRuleMultipleInteractionsTest.java` 213 | ```java 214 | @RunWith(SpringRunner.class) 215 | @SpringBootTest 216 | public class PactJunitRuleMultipleInteractionsTest { 217 | 218 | @Autowired 219 | ProviderService providerService; 220 | 221 | @Rule 222 | public PactProviderRule mockProvider = new PactProviderRule("ExampleProvider",this); 223 | 224 | @Pact(consumer="JunitRuleMultipleInteractionsConsumer") 225 | public RequestResponsePact createPact(PactDslWithProvider builder) { 226 | Map headers = new HashMap(); 227 | headers.put("Content-Type", "application/json;charset=UTF-8"); 228 | 229 | return builder 230 | .given("") 231 | .uponReceiving("Miku") 232 | .path("/information") 233 | .query("name=Miku") 234 | .method("GET") 235 | .willRespondWith() 236 | .headers(headers) 237 | .status(200) 238 | .body("{\n" + 239 | " \"salary\": 45000,\n" + 240 | " \"name\": \"Hatsune Miku\",\n" + 241 | " \"nationality\": \"Japan\",\n" + 242 | " \"contact\": {\n" + 243 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 244 | " \"Phone Number\": \"9090950\"\n" + 245 | " }\n" + 246 | "}") 247 | .given("") 248 | .uponReceiving("Nanoha") 249 | .path("/information") 250 | .query("name=Nanoha") 251 | .method("GET") 252 | .willRespondWith() 253 | .headers(headers) 254 | .status(200) 255 | .body("{\n" + 256 | " \"salary\": 80000,\n" + 257 | " \"name\": \"Takamachi Nanoha\",\n" + 258 | " \"nationality\": \"Japan\",\n" + 259 | " \"contact\": {\n" + 260 | " \"Email\": \"takamachi.nanoha@ariman.com\",\n" + 261 | " \"Phone Number\": \"9090940\"\n" + 262 | " }\n" + 263 | "}") 264 | .toPact(); 265 | } 266 | 267 | @Test 268 | @PactVerification() 269 | public void runTest() { 270 | providerService.setBackendURL(mockProvider.getUrl()); 271 | Information information = providerService.getInformation(); 272 | assertEquals(information.getName(), "Hatsune Miku"); 273 | 274 | providerService.setBackendURL(mockProvider.getUrl(), "Nanoha"); 275 | information = providerService.getInformation(); 276 | assertEquals(information.getName(), "Takamachi Nanoha"); 277 | } 278 | } 279 | ``` 280 | This case uses Junit Rule too, but with two interactions in one Pact file. 281 | 282 | #### 2.1.3. Junit DSL 283 | `PactJunitDSLTest` 284 | ```java 285 | @RunWith(SpringRunner.class) 286 | @SpringBootTest 287 | public class PactJunitDSLTest { 288 | 289 | @Autowired 290 | ProviderService providerService; 291 | 292 | private void checkResult(PactVerificationResult result) { 293 | if (result instanceof PactVerificationResult.Error) { 294 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 295 | } 296 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 297 | } 298 | 299 | @Test 300 | public void testPact1() { 301 | Map headers = new HashMap(); 302 | headers.put("Content-Type", "application/json;charset=UTF-8"); 303 | 304 | RequestResponsePact pact = ConsumerPactBuilder 305 | .consumer("JunitDSLConsumer1") 306 | .hasPactWith("ExampleProvider") 307 | .given("") 308 | .uponReceiving("Query name is Miku") 309 | .path("/information") 310 | .query("name=Miku") 311 | .method("GET") 312 | .willRespondWith() 313 | .headers(headers) 314 | .status(200) 315 | .body("{\n" + 316 | " \"salary\": 45000,\n" + 317 | " \"name\": \"Hatsune Miku\",\n" + 318 | " \"nationality\": \"Japan\",\n" + 319 | " \"contact\": {\n" + 320 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 321 | " \"Phone Number\": \"9090950\"\n" + 322 | " }\n" + 323 | "}") 324 | .toPact(); 325 | 326 | MockProviderConfig config = MockProviderConfig.createDefault(); 327 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 328 | providerService.setBackendURL(mockServer.getUrl(), "Miku"); 329 | Information information = providerService.getInformation(); 330 | assertEquals(information.getName(), "Hatsune Miku"); 331 | return null; 332 | }); 333 | 334 | checkResult(result); 335 | } 336 | 337 | @Test 338 | public void testPact2() { 339 | Map headers = new HashMap(); 340 | headers.put("Content-Type", "application/json;charset=UTF-8"); 341 | 342 | RequestResponsePact pact = ConsumerPactBuilder 343 | .consumer("JunitDSLConsumer2") 344 | .hasPactWith("ExampleProvider") 345 | .given("") 346 | .uponReceiving("Query name is Nanoha") 347 | .path("/information") 348 | .query("name=Nanoha") 349 | .method("GET") 350 | .willRespondWith() 351 | .headers(headers) 352 | .status(200) 353 | .body("{\n" + 354 | " \"salary\": 80000,\n" + 355 | " \"name\": \"Takamachi Nanoha\",\n" + 356 | " \"nationality\": \"Japan\",\n" + 357 | " \"contact\": {\n" + 358 | " \"Email\": \"takamachi.nanoha@ariman.com\",\n" + 359 | " \"Phone Number\": \"9090940\"\n" + 360 | " }\n" + 361 | "}") 362 | .toPact(); 363 | 364 | MockProviderConfig config = MockProviderConfig.createDefault(); 365 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 366 | providerService.setBackendURL(mockServer.getUrl(), "Nanoha"); 367 | Information information = providerService.getInformation(); 368 | assertEquals(information.getName(), "Takamachi Nanoha"); 369 | return null; 370 | }); 371 | 372 | checkResult(result); 373 | } 374 | } 375 | ``` 376 | Comparing with Basic Junit and Junit Rule usage, the DSL provides the ability to create multiple Pact files 377 | in one test class. 378 | 379 | `PactJunitDSLJsonBodyTest` 380 | ```java 381 | @RunWith(SpringRunner.class) 382 | @SpringBootTest 383 | public class PactJunitDSLJsonBodyTest { 384 | 385 | @Autowired 386 | ProviderService providerService; 387 | 388 | private void checkResult(PactVerificationResult result) { 389 | if (result instanceof PactVerificationResult.Error) { 390 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 391 | } 392 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 393 | } 394 | 395 | @Test 396 | public void testWithPactDSLJsonBody() { 397 | Map headers = new HashMap(); 398 | headers.put("Content-Type", "application/json;charset=UTF-8"); 399 | 400 | DslPart body = new PactDslJsonBody() 401 | .numberType("salary", 45000) 402 | .stringType("name", "Hatsune Miku") 403 | .stringType("nationality", "Japan") 404 | .object("contact") 405 | .stringValue("Email", "hatsune.miku@ariman.com") 406 | .stringValue("Phone Number", "9090950") 407 | .closeObject(); 408 | 409 | RequestResponsePact pact = ConsumerPactBuilder 410 | .consumer("JunitDSLJsonBodyConsumer") 411 | .hasPactWith("ExampleProvider") 412 | .given("") 413 | .uponReceiving("Query name is Miku") 414 | .path("/information") 415 | .query("name=Miku") 416 | .method("GET") 417 | .willRespondWith() 418 | .headers(headers) 419 | .status(200) 420 | .body(body) 421 | .toPact(); 422 | 423 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 424 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 425 | providerService.setBackendURL(mockServer.getUrl()); 426 | Information information = providerService.getInformation(); 427 | assertEquals(information.getName(), "Hatsune Miku"); 428 | return null; 429 | }); 430 | 431 | checkResult(result); 432 | } 433 | 434 | @Test 435 | public void testWithLambdaDSLJsonBody() { 436 | Map headers = new HashMap(); 437 | headers.put("Content-Type", "application/json;charset=UTF-8"); 438 | 439 | DslPart body = newJsonBody((root) -> { 440 | root.numberValue("salary", 45000); 441 | root.stringValue("name", "Hatsune Miku"); 442 | root.stringValue("nationality", "Japan"); 443 | root.object("contact", (contactObject) -> { 444 | contactObject.stringMatcher("Email", ".*@ariman.com", "hatsune.miku@ariman.com"); 445 | contactObject.stringType("Phone Number", "9090950"); 446 | }); 447 | }).build(); 448 | 449 | RequestResponsePact pact = ConsumerPactBuilder 450 | .consumer("JunitDSLLambdaJsonBodyConsumer") 451 | .hasPactWith("ExampleProvider") 452 | .given("") 453 | .uponReceiving("Query name is Miku") 454 | .path("/information") 455 | .query("name=Miku") 456 | .method("GET") 457 | .willRespondWith() 458 | .headers(headers) 459 | .status(200) 460 | .body(body) 461 | .toPact(); 462 | 463 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 464 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 465 | providerService.setBackendURL(mockServer.getUrl()); 466 | Information information = providerService.getInformation(); 467 | assertEquals(information.getName(), "Hatsune Miku"); 468 | return null; 469 | }); 470 | 471 | checkResult(result); 472 | } 473 | } 474 | ``` 475 | When use Json Body in DSL usage, we can control the test accuracy by defining whether we check the response 476 | body's attributes by Value or by Type, or even with Regular Expression. Comparing with normal Json body, the 477 | Lambda statements can avoid using `.close**()` methods to make the codes more clean. You can find more 478 | description about it [here](https://github.com/DiUS/pact-jvm/tree/be4a32b08ebbd89321dc37cbfe838fdac37774b3/pact-jvm-consumer-java8). 479 | 480 | ### 2.2. Run the Tests at Consumer Miku side 481 | Because we are using Junit, so to run the tests and create Pact files are very easy, just as what we always 482 | run our usual Unit Test: 483 | 484 | `./gradlew :example-consumer-miku:clean test` 485 | 486 | After that, you can find 7 JSON files created in folder `Pacts\Miku`. These are the Pacts which contain the 487 | contract between Miku and Provider, and these Pacts will be used to drive the Contract Test later at Provider 488 | side. 489 | 490 | ### 2.3. Publish Pacts to Pact Broker 491 | The JSON files generated with task `test` are in the local folder which are only reachable to our local Provider, 492 | while for real project practice, it's highly recommended to use [Pact Broker](https://github.com/pact-foundation/pact_broker) 493 | to transport the Pacts between Consumers and Providers. 494 | 495 | > There's a `docker-compose.yml` file that aids setting up an instance of the Pact Broker. 496 | > Run `docker-compose up` and you'll have a Broker running at `http://localhost/`. 497 | > It'll set up an instance of PostgreSQL as well, but the data will be lost at every restart. 498 | > In order to use it, in `build.gradle`, set `pactBrokerUrl` to `http://localhost` and both `pactBrokerUsername` and `pactBrokerPassword` to `''`. 499 | 500 | After you set up a Pact Broker server for your own, you can easily share your Pacts to the broker with the `pactPublish` command: 501 | 502 | `./gradlew :example-consumer-miku:pactPublish` 503 | 504 | This command will upload your Pacts to the broker server: 505 | 506 | ```commandline 507 | > Task :example-consumer-miku:pactPublish 508 | Publishing JunitDSLConsumer1-ExampleProvider.json ... HTTP/1.1 200 OK 509 | Publishing JunitDSLJsonBodyConsumer-ExampleProvider.json ... HTTP/1.1 200 OK 510 | Publishing JunitDSLLambdaJsonBodyConsumer-ExampleProvider.json ... HTTP/1.1 200 OK 511 | Publishing BaseConsumer-ExampleProvider.json ... HTTP/1.1 200 OK 512 | Publishing JunitRuleConsumer-ExampleProvider.json ... HTTP/1.1 200 OK 513 | Publishing JunitRuleMultipleInteractionsConsumer-ExampleProvider.json ... HTTP/1.1 200 OK 514 | Publishing JunitDSLConsumer2-ExampleProvider.json ... HTTP/1.1 200 OK 515 | ``` 516 | 517 | Then you can find the *Relationships* in [our Pact Broker](https://ariman.pact.dius.com.au/) 518 | 519 | > you can find their are '7' consumers to 1 provider, in real project, it should NOT like that, because we have only one 520 | consumer Miku here, it should be only 1 consumer to 1 provider, while in this example, making it's '7' is only to show 521 | that how Pact Broker can display the relationships beautifully. 522 | 523 | ![image](https://github.com/Mikuu/Pact-JVM-Example/blob/master/screenshot/pact-broker.png) 524 | 525 | Later, our Provider can fetch these Pacts from broker to drive the Contract Test. 526 | 527 | ### 2.4. Run the Contract Test at Provider side 528 | We are using the Pact Gradle task in Provider side to run the Contract Test, which can be very easy without 529 | writing any code, just execute the `pactVerify` task. **Before we run the test, make sure the Provider API is already started and running at our localhost.** 530 | 531 | Then we can run the test as: 532 | 533 | `./gradlew :example-provider:pactVerify` 534 | 535 | and you can find the test results at the command line, would be something likes: 536 | 537 | ```commandline 538 | Arimans-MacBook-Pro:pact-jvm-example ariman$ ./gradlew :example-provider:pactVerify 539 | 540 | > Task :example-provider:pactVerify_ExampleProvider 541 | 542 | Verifying a pact between Miku - Base contract and ExampleProvider 543 | [Using File /Users/ariman/Workspace/Pacting/pact-jvm-example/Pacts/Miku/BaseConsumer-ExampleProvider.json] 544 | Given 545 | WARNING: State Change ignored as there is no stateChange URL 546 | Consumer Miku 547 | returns a response which 548 | has status code 200 (OK) 549 | includes headers 550 | "Content-Type" with value "application/json;charset=UTF-8" (OK) 551 | has a matching body (OK) 552 | Given 553 | WARNING: State Change ignored as there is no stateChange URL 554 | Pact JVM example Pact interaction 555 | returns a response which 556 | has status code 200 (OK) 557 | includes headers 558 | "Content-Type" with value "application/json;charset=UTF-8" (OK) 559 | has a matching body (OK) 560 | 561 | ... 562 | 563 | Verifying a pact between JunitRuleMultipleInteractionsConsumer and ExampleProvider 564 | [from Pact Broker https://ariman.pact.dius.com.au/pacts/provider/ExampleProvider/consumer/JunitRuleMultipleInteractionsConsumer/version/1.0.0] 565 | Given 566 | WARNING: State Change ignored as there is no stateChange URL 567 | Miku 568 | returns a response which 569 | has status code 200 (OK) 570 | includes headers 571 | "Content-Type" with value "application/json;charset=UTF-8" (OK) 572 | has a matching body (OK) 573 | Given 574 | WARNING: State Change ignored as there is no stateChange URL 575 | Nanoha 576 | returns a response which 577 | has status code 200 (OK) 578 | includes headers 579 | "Content-Type" with value "application/json;charset=UTF-8" (OK) 580 | has a matching body (OK) 581 | 582 | 583 | ``` 584 | 585 | You can find the tests checked the Pacts from both local and remote Pact Broker. 586 | 587 | > Since this example is open to all Pact learners, means everyone can upload their own Pacts to this Pact Broker, including 588 | correct and incorrect pacts, so don't be surprised if your tests run failed with the pacts from this Pact Broker. You can 589 | always upload correct pacts to the broker, but just don't rely on it, the pacts in this broker may be cleared at anytime 590 | for reset a clean environment. 591 | 592 | ## 3. Gradle Configuration 593 | 594 | Before we continue to Consumer Nanoha, let's look at the Gradle Configuration first: 595 | 596 | ```groovy 597 | project(':example-consumer-miku') { 598 | ... 599 | test { 600 | systemProperties['pact.rootDir'] = "$rootDir/Pacts/Miku" 601 | } 602 | 603 | pact { 604 | publish { 605 | pactDirectory = "$rootDir/Pacts/Miku" 606 | pactBrokerUrl = mybrokerUrl 607 | pactBrokerUsername = mybrokerUser 608 | pactBrokerPassword = mybrokerPassword 609 | } 610 | } 611 | ... 612 | } 613 | 614 | 615 | project(':example-consumer-nanoha') { 616 | ... 617 | test { 618 | systemProperties['pact.rootDir'] = "$rootDir/Pacts/Nanoha" 619 | } 620 | ... 621 | } 622 | 623 | import java.net.URL 624 | 625 | project(':example-provider') { 626 | ... 627 | pact { 628 | serviceProviders { 629 | ExampleProvider { 630 | protocol = 'http' 631 | host = 'localhost' 632 | port = 8080 633 | path = '/' 634 | 635 | // Test Pacts from local Miku 636 | hasPactWith('Miku - Base contract') { 637 | pactSource = file("$rootDir/Pacts/Miku/BaseConsumer-ExampleProvider.json") 638 | } 639 | 640 | hasPactsWith('Miku - All contracts') { 641 | pactFileLocation = file("$rootDir/Pacts/Miku") 642 | } 643 | 644 | // Test Pacts from Pact Broker 645 | hasPactsFromPactBroker(mybrokerUrl, authentication: ['Basic', mybrokerUser, mybrokerPassword]) 646 | 647 | // Test Pacts from local Nanoha 648 | // hasPactWith('Nanoha - With Nantionality') { 649 | // pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json") 650 | // } 651 | 652 | // hasPactWith('Nanoha - No Nantionality') { 653 | // stateChangeUrl = new URL('http://localhost:8080/pactStateChange') 654 | // pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json") 655 | // } 656 | } 657 | } 658 | } 659 | } 660 | ``` 661 | 662 | Here we have configuration separately for Consumer Nanoha, Consumer Miku and Provider. 663 | 664 | - `systemProperties['pact.rootDir']` defines the path where consumer Miku and Nanoha generate their Pacts files locally. 665 | 666 | 667 | - `pact { ... }` defines the the Pact Broker URL and where to fetch pacts to upload to the broker. 668 | 669 | - Then in Provider configuration, `hasPactWith()` and `hasPactsWith()` specify the local path to find Pact files, 670 | and `hasPactsFromPactBroker` specify the remote Pact Broker to fetch Pact files. 671 | 672 | *Why comment Nanoha's Pacts path?* Because we haven't created Nanoha's Pacts, so it will raise exception with 673 | that configuration, we can uncomment that path later after we created the Nanoha's Pacts files. 674 | 675 | ## 4. Contract Test between Provider and Consumer Nanoha 676 | 677 | The Contract Test between Nanoha and Provider is similar to Miku, the difference I want to demonstrate here is 678 | **Provider State**, you can find more detailed explanation about the Provider State [here](https://docs.pact.io/documentation/provider_states.html). 679 | 680 | ### 4.1. Preparation for Provider State 681 | 682 | In our example Provider, the returned payload for both Miku and Nanoha have a property `.nationality`, this kind 683 | of properties values, in real project, could always be queried from database or dependency service, but in this 684 | example, to simplify the Provider application logic, I just create a static property to simulate the data storage: 685 | 686 | `provider.ulti.Nationality` 687 | 688 | ```java 689 | public class Nationality { 690 | private static String nationality = "Japan"; 691 | 692 | public static String getNationality() { 693 | return nationality; 694 | } 695 | 696 | public static void setNationality(String nationality) { 697 | Nationality.nationality = nationality; 698 | } 699 | } 700 | ``` 701 | 702 | Then we can change the `.nationality` value at anytime to achieve our test purpose. To change its value, I created 703 | a `PactController` which can accept and reset the state value by sending a POST to endpoint `/pactStateChange`: 704 | 705 | `provider.PactController` 706 | 707 | ```java 708 | @Profile("pact") 709 | @RestController 710 | public class PactController { 711 | 712 | @RequestMapping(value = "/pactStateChange", method = RequestMethod.POST) 713 | public PactStateChangeResponseDTO providerState(@RequestBody PactState body) { 714 | switch (body.getState()) { 715 | case "No nationality": 716 | Nationality.setNationality(null); 717 | System.out.println("Pact State Change >> remove nationality ..."); 718 | break; 719 | case "Default nationality": 720 | Nationality.setNationality("Japan"); 721 | System.out.println("Pact Sate Change >> set default nationality ..."); 722 | break; 723 | } 724 | 725 | // This response is not mandatory for Pact state change. The only reason is the current Pact-JVM v4.0.4 does 726 | // check the stateChange request's response, more exactly, checking the response's Content-Type, couldn't be 727 | // null, so it MUST return something here. 728 | PactStateChangeResponseDTO pactStateChangeResponse = new PactStateChangeResponseDTO(); 729 | pactStateChangeResponse.setState(body.getState()); 730 | 731 | return pactStateChangeResponse; 732 | } 733 | } 734 | ``` 735 | 736 | Because this controller should only be available at **Non-Production environment**, so I added a Profile annotation 737 | to limit that this endpoint is only existing when Provider application is running with a "pact" profile, in another 738 | words, it only works in test environment. 739 | 740 | **So, to summary all above things, when Provider is running with a "pact" profile, it serves an endpoint `/pactStateChange` 741 | which accept a POST request to set the `.nationality` value to be "Japan" (by default) or "null"**. 742 | 743 | ### 4.2. Create Test Case at Consumer Nanoha side 744 | 745 | The test cases at Nanoha side is using DSL Lambda, here we have two cases in our test, the main difference between 746 | them is the expectation for `.nationality` and the Provider State we set in `.given()`. 747 | 748 | ```java 749 | @RunWith(SpringRunner.class) 750 | @SpringBootTest 751 | public class NationalityPactTest { 752 | 753 | @Autowired 754 | ProviderService providerService; 755 | 756 | private void checkResult(PactVerificationResult result) { 757 | if (result instanceof PactVerificationResult.Error) { 758 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 759 | } 760 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 761 | } 762 | 763 | @Test 764 | public void testWithNationality() { 765 | Map headers = new HashMap(); 766 | headers.put("Content-Type", "application/json;charset=UTF-8"); 767 | 768 | DslPart body = newJsonBody((root) -> { 769 | root.numberType("salary"); 770 | root.stringValue("name", "Takamachi Nanoha"); 771 | root.stringValue("nationality", "Japan"); 772 | root.object("contact", (contactObject) -> { 773 | contactObject.stringMatcher("Email", ".*@ariman.com", "takamachi.nanoha@ariman.com"); 774 | contactObject.stringType("Phone Number", "9090940"); 775 | }); 776 | }).build(); 777 | 778 | RequestResponsePact pact = ConsumerPactBuilder 779 | .consumer("ConsumerNanohaWithNationality") 780 | .hasPactWith("ExampleProvider") 781 | .given("") 782 | .uponReceiving("Query name is Nanoha") 783 | .path("/information") 784 | .query("name=Nanoha") 785 | .method("GET") 786 | .willRespondWith() 787 | .headers(headers) 788 | .status(200) 789 | .body(body) 790 | .toPact(); 791 | 792 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 793 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 794 | providerService.setBackendURL(mockServer.getUrl()); 795 | Information information = providerService.getInformation(); 796 | assertEquals(information.getName(), "Takamachi Nanoha"); 797 | assertEquals(information.getNationality(), "Japan"); 798 | return null; 799 | }); 800 | 801 | checkResult(result); 802 | } 803 | 804 | @Test 805 | public void testNoNationality() { 806 | Map headers = new HashMap(); 807 | headers.put("Content-Type", "application/json;charset=UTF-8"); 808 | 809 | DslPart body = newJsonBody((root) -> { 810 | root.numberType("salary"); 811 | root.stringValue("name", "Takamachi Nanoha"); 812 | root.stringValue("nationality", null); 813 | root.object("contact", (contactObject) -> { 814 | contactObject.stringMatcher("Email", ".*@ariman.com", "takamachi.nanoha@ariman.com"); 815 | contactObject.stringType("Phone Number", "9090940"); 816 | }); 817 | }).build(); 818 | 819 | RequestResponsePact pact = ConsumerPactBuilder 820 | .consumer("ConsumerNanohaNoNationality") 821 | .hasPactWith("ExampleProvider") 822 | .given("No nationality") 823 | .uponReceiving("Query name is Nanoha") 824 | .path("/information") 825 | .query("name=Nanoha") 826 | .method("GET") 827 | .willRespondWith() 828 | .headers(headers) 829 | .status(200) 830 | .body(body) 831 | .toPact(); 832 | 833 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 834 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 835 | providerService.setBackendURL(mockServer.getUrl()); 836 | Information information = providerService.getInformation(); 837 | assertEquals(information.getName(), "Takamachi Nanoha"); 838 | assertNull(information.getNationality()); 839 | return null; 840 | }); 841 | 842 | checkResult(result); 843 | } 844 | } 845 | ``` 846 | 847 | To run the test: 848 | 849 | ```commandline 850 | ./gradlew :example-consumer-nanoha:clean test 851 | ``` 852 | 853 | Then you can find two Pacts files generated at `Pacts\Nanoha`. 854 | 855 | ### 4.3. Run Contract Test at Provider side 856 | 857 | #### 4.3.1. start Provider application 858 | 859 | As we mentioned above, before we running our test, we need start Provider application with profile "pact" (if your 860 | Provider application is already started when you ran test with Miku, please kill it first), the command 861 | is: 862 | 863 | ```commandline 864 | SPRING_PROFILES_ACTIVE=pact ./gradlew :example-provider:bootRun 865 | ``` 866 | 867 | #### 4.3.2. update Gradle configuration 868 | 869 | it's time to uncomment the Pacts file searching path in Provider configuration: 870 | 871 | `build.gralde` 872 | 873 | ```groovy 874 | 875 | hasPactWith('Nanoha - With Nantionality') { 876 | pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json") 877 | } 878 | 879 | hasPactWith('Nanoha - No Nantionality') { 880 | stateChangeUrl = new URL('http://localhost:8080/pactStateChange') 881 | pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json") 882 | } 883 | ``` 884 | 885 | The first Pact will check the payload with default `.nationality` which value is `Japan`, while the second Pact, we 886 | define a `stateChangeUrl` which is implemented by `PactController` in Provider as we talked above, and the POST body 887 | is defined in `.given()` in the Pact. 888 | 889 | #### 4.3.3. run the contract test 890 | 891 | the same command as we did with Consumer Miku: 892 | 893 | ```commandline 894 | ./gradlew :example-provider:pactVerify 895 | ``` 896 | 897 | Then you can get the test log in the same terminal. 898 | 899 | ## 5. Break Something 900 | 901 | Is everything done? Yes, if you followed all above introductions until here, you should be able to create your Contract 902 | Test by yourself. But before you go to real, let's try to break something to see how Pact could find the defect. 903 | 904 | ### 5.1. Break something in Provider 905 | 906 | Both Miku and Nanoha consume Provider's response for a property `.name`, in real project, there might be a case that 907 | Provider would like to change this property name to `.fullname`, this could be a classic Contract Breaking which would 908 | be captured by Pact. 909 | 910 | To do that, we need modify several lines of codes in Provider which could be a little difficult to 911 | understand, especially for a some testers who are not familiar to Spring Boot application. 912 | 913 | So to make the breaking simple, let's just force Provider returns `null` as the value to `.name` to all consumers, this can be easily done by 914 | adding only a single line: 915 | 916 | `provider.InformationController` 917 | ```java 918 | @RestController 919 | public class InformationController { 920 | ... 921 | 922 | information.setName(null); 923 | return information; 924 | } 925 | } 926 | ``` 927 | 928 | ### 5.2. Retest 929 | 930 | Restart your Provider application and run the test again, you can get the test failures as this: 931 | 932 | ```commandline 933 | ... 934 | 935 | Verifying a pact between Nanoha - With Nantionality and ExampleProvider 936 | [Using File /Users/Biao/Workspace/Pacting/Pact-JVM-Example/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json] 937 | Given 938 | WARNING: State Change ignored as there is no stateChange URL 939 | Query name is Nanoha 940 | returns a response which 941 | has status code 200 (OK) 942 | has a matching body (FAILED) 943 | 944 | Failures: 945 | 946 | 0) Verifying a pact between Nanoha - With Nantionality and ExampleProvider - Query name is Nanoha Given returns a response which has a matching body 947 | 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, + }, }]) 948 | 949 | ... 950 | ``` 951 | 952 | ## 6. Contribution 953 | 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 it 954 | into this example, any PR is welcome. 955 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE") 7 | classpath("org.thymeleaf:thymeleaf:3.0.8.RELEASE") 8 | classpath("au.com.dius:pact-jvm-provider-gradle:4.0.3") 9 | } 10 | } 11 | 12 | allprojects { 13 | apply plugin: 'java' 14 | apply plugin: 'idea' 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | } 20 | 21 | subprojects { 22 | apply plugin: 'org.springframework.boot' 23 | 24 | sourceCompatibility = 1.8 25 | targetCompatibility = 1.8 26 | 27 | dependencies { 28 | implementation("org.springframework.boot:spring-boot-starter-web") 29 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf") 30 | implementation("org.springframework.boot:spring-boot-devtools") 31 | testImplementation('org.springframework.boot:spring-boot-starter-test') 32 | testImplementation("junit:junit") 33 | testImplementation("au.com.dius:pact-jvm-consumer-junit:4.0.3") 34 | testImplementation("au.com.dius:pact-jvm-consumer-java8:4.0.3") 35 | } 36 | 37 | } 38 | 39 | project(':example-consumer-miku') { 40 | version '1.0.0' 41 | apply plugin: 'au.com.dius.pact' 42 | 43 | jar { 44 | archiveBaseName = 'example-consumer-miku' 45 | archiveVersion = '1.0.0' 46 | } 47 | 48 | test { 49 | systemProperties['pact.rootDir'] = "$rootDir/Pacts/Miku" 50 | } 51 | 52 | pact { 53 | publish { 54 | pactDirectory = "$rootDir/Pacts/Miku" 55 | 56 | pactBrokerUrl = mybrokerUrl 57 | pactBrokerUsername = mybrokerUser 58 | pactBrokerPassword = mybrokerPassword 59 | 60 | // This is to local Pact Broker in Docker 61 | // pactBrokerUrl = "http://localhost" 62 | } 63 | } 64 | } 65 | 66 | project(':example-consumer-nanoha') { 67 | jar { 68 | archiveBaseName = 'example-consumer-nanoha' 69 | archiveVersion = '1.0.0' 70 | } 71 | 72 | test { 73 | systemProperties['pact.rootDir'] = "$rootDir/Pacts/Nanoha" 74 | } 75 | } 76 | 77 | import java.net.URL 78 | 79 | project(':example-provider') { 80 | apply plugin: 'au.com.dius.pact' 81 | 82 | jar { 83 | archiveBaseName = 'example-provider' 84 | archiveVersion = '1.0.0' 85 | } 86 | 87 | pact { 88 | serviceProviders { 89 | ExampleProvider { 90 | protocol = 'http' 91 | host = 'localhost' 92 | port = 8080 93 | path = '/' 94 | 95 | // Test Pacts from local Miku 96 | hasPactWith('Miku - Base contract') { 97 | pactSource = file("$rootDir/Pacts/Miku/BaseConsumer-ExampleProvider.json") 98 | } 99 | 100 | hasPactsWith('Miku - All contracts') { 101 | pactFileLocation = file("$rootDir/Pacts/Miku") 102 | } 103 | 104 | // Test Pacts from Pact Broker 105 | hasPactsFromPactBroker(mybrokerUrl, authentication: ['Basic', mybrokerUser, mybrokerPassword]) 106 | 107 | // This is for local Pact Broker in Docker 108 | //hasPactsFromPactBroker("http://localhost") 109 | 110 | // Test Pacts from local Nanoha 111 | // hasPactWith('Nanoha - With Nantionality') { 112 | // pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaWithNationality-ExampleProvider.json") 113 | // } 114 | 115 | // hasPactWith('Nanoha - No Nantionality') { 116 | // stateChangeUrl = new URL('http://localhost:8080/pactStateChange') 117 | // pactSource = file("$rootDir/Pacts/Nanoha/ConsumerNanohaNoNationality-ExampleProvider.json") 118 | // } 119 | 120 | } 121 | } 122 | } 123 | 124 | } 125 | 126 | 127 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | postgres: 6 | image: "postgres:9.6.3" 7 | ports: 8 | - "5432:5432" 9 | environment: 10 | POSTGRES_USER: postgres 11 | POSTGRES_PASSWORD: password 12 | 13 | broker_app: 14 | image: dius/pact-broker 15 | ports: 16 | - "80:80" 17 | links: 18 | - postgres 19 | environment: 20 | PACT_BROKER_DATABASE_USERNAME: postgres 21 | PACT_BROKER_DATABASE_PASSWORD: password 22 | PACT_BROKER_DATABASE_HOST: postgres 23 | PACT_BROKER_DATABASE_NAME: postgres 24 | -------------------------------------------------------------------------------- /example-consumer-miku/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8081 2 | spring.thymeleaf.mode=HTML -------------------------------------------------------------------------------- /example-consumer-miku/gradle.properties: -------------------------------------------------------------------------------- 1 | thymeleaf.version=3.0.8.RELEASE 2 | thymeleaf-layout-dialect.version=2.2.2 3 | -------------------------------------------------------------------------------- /example-consumer-miku/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example-consumer-miku' 2 | project.version = '1.0.0' 3 | 4 | -------------------------------------------------------------------------------- /example-consumer-miku/src/main/java/ariman/pact/consumer/Application.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /example-consumer-miku/src/main/java/ariman/pact/consumer/Information.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Information { 7 | private Integer salary; 8 | private String name; 9 | private String nationality; 10 | private Map contact = new HashMap(); 11 | 12 | public Integer getSalary() { 13 | return salary; 14 | } 15 | 16 | public void setSalary(Integer salary) { 17 | this.salary = salary; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public Map getContact() { 29 | return contact; 30 | } 31 | 32 | public void setContact(Map contact) { 33 | this.contact = contact; 34 | } 35 | 36 | public String getNationality() { 37 | return nationality; 38 | } 39 | 40 | public void setNationality(String nationality) { 41 | this.nationality = nationality; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example-consumer-miku/src/main/java/ariman/pact/consumer/InformationController.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | @Controller 9 | public class InformationController { 10 | 11 | @Autowired 12 | private ProviderService providerService; 13 | 14 | @RequestMapping("/miku") 15 | public String miku(Model model) { 16 | Information information = providerService.getInformation(); 17 | model.addAttribute("name", information.getName()); 18 | model.addAttribute("mail", information.getContact().get("Email")); 19 | model.addAttribute("phone", information.getContact().get("Phone Number")); 20 | 21 | return "miku"; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /example-consumer-miku/src/main/java/ariman/pact/consumer/ProviderService.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.web.client.RestTemplate; 5 | 6 | @Service 7 | public class ProviderService { 8 | 9 | private String backendURL = "http://localhost:8080/information?name=Miku"; 10 | 11 | public String getBackendURL() { 12 | return this.backendURL; 13 | } 14 | 15 | public void setBackendURL(String URLBase) { 16 | this.backendURL = URLBase+"/information?name=Miku"; 17 | } 18 | public void setBackendURL(String URLBase, String name) { 19 | this.backendURL = URLBase+"/information?name="+name; 20 | } 21 | 22 | public Information getInformation() { 23 | RestTemplate restTemplate = new RestTemplate(); 24 | Information information = restTemplate.getForObject(getBackendURL(), Information.class); 25 | 26 | return information; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example-consumer-miku/src/main/resources/static/css/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-secondary, 14 | .btn-secondary:hover, 15 | .btn-secondary:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritance from `body` */ 18 | background-color: #fff; 19 | border: .05rem solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | background-color: #333; 31 | } 32 | body { 33 | color: #fff; 34 | text-align: center; 35 | text-shadow: 0 .05rem .1rem rgba(0,0,0,.5); 36 | } 37 | 38 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 39 | .site-wrapper { 40 | display: table; 41 | width: 100%; 42 | height: 100%; /* For at least Firefox */ 43 | min-height: 100%; 44 | box-shadow: inset 0 0 5rem rgba(0,0,0,.5); 45 | } 46 | .site-wrapper-inner { 47 | display: table-cell; 48 | vertical-align: top; 49 | } 50 | .cover-container { 51 | margin-right: auto; 52 | margin-left: auto; 53 | } 54 | 55 | /* Padding for spacing */ 56 | .inner { 57 | padding: 2rem; 58 | } 59 | 60 | 61 | /* 62 | * Header 63 | */ 64 | 65 | .masthead { 66 | margin-bottom: 2rem; 67 | } 68 | 69 | .masthead-brand { 70 | margin-bottom: 0; 71 | } 72 | 73 | .nav-masthead .nav-link { 74 | padding: .25rem 0; 75 | font-weight: 700; 76 | color: rgba(255,255,255,.5); 77 | background-color: transparent; 78 | border-bottom: .25rem solid transparent; 79 | } 80 | 81 | .nav-masthead .nav-link:hover, 82 | .nav-masthead .nav-link:focus { 83 | border-bottom-color: rgba(255,255,255,.25); 84 | } 85 | 86 | .nav-masthead .nav-link + .nav-link { 87 | margin-left: 1rem; 88 | } 89 | 90 | .nav-masthead .active { 91 | color: #fff; 92 | border-bottom-color: #fff; 93 | } 94 | 95 | @media (min-width: 48em) { 96 | .masthead-brand { 97 | float: left; 98 | } 99 | .nav-masthead { 100 | float: right; 101 | } 102 | } 103 | 104 | 105 | /* 106 | * Cover 107 | */ 108 | 109 | .cover { 110 | padding: 0 1.5rem; 111 | } 112 | .cover .btn-lg { 113 | padding: .75rem 1.25rem; 114 | font-weight: 700; 115 | } 116 | 117 | 118 | /* 119 | * Footer 120 | */ 121 | 122 | .mastfoot { 123 | color: rgba(255,255,255,.5); 124 | } 125 | 126 | 127 | /* 128 | * Affix and center 129 | */ 130 | 131 | @media (min-width: 40em) { 132 | /* Pull out the header and footer */ 133 | .masthead { 134 | position: fixed; 135 | top: 0; 136 | } 137 | .mastfoot { 138 | position: fixed; 139 | bottom: 0; 140 | } 141 | 142 | /* Start the vertical centering */ 143 | .site-wrapper-inner { 144 | vertical-align: middle; 145 | } 146 | 147 | /* Handle the widths */ 148 | .masthead, 149 | .mastfoot, 150 | .cover-container { 151 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 152 | } 153 | } 154 | 155 | @media (min-width: 62em) { 156 | .masthead, 157 | .mastfoot, 158 | .cover-container { 159 | width: 42rem; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /example-consumer-miku/src/main/resources/static/img/miku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/example-consumer-miku/src/main/resources/static/img/miku.png -------------------------------------------------------------------------------- /example-consumer-miku/src/main/resources/templates/miku.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Cover Template for Bootstrap 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 |

Ariman

31 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |

47 |

48 |

49 |

50 |
51 |
52 | 53 |
54 |
55 |

Example Page for Pact JVM.

56 |
57 |
58 | 59 |
60 | 61 |
62 | 63 |
64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /example-consumer-miku/src/test/java/ariman/pact/consumer/PactBaseConsumerTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.MockServer; 4 | import au.com.dius.pact.consumer.PactTestExecutionContext; 5 | import au.com.dius.pact.consumer.dsl.PactDslWithProvider; 6 | import au.com.dius.pact.consumer.junit.ConsumerPactTest; 7 | import au.com.dius.pact.core.model.RequestResponsePact; 8 | import au.com.dius.pact.core.model.annotations.Pact; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | public class PactBaseConsumerTest extends ConsumerPactTest { 22 | 23 | @Autowired 24 | ProviderService providerService; 25 | 26 | @Override 27 | @Pact(provider="ExampleProvider", consumer="BaseConsumer") 28 | public RequestResponsePact createPact(PactDslWithProvider builder) { 29 | Map headers = new HashMap(); 30 | headers.put("Content-Type", "application/json;charset=UTF-8"); 31 | 32 | return builder 33 | .given("") 34 | .uponReceiving("Pact JVM example Pact interaction") 35 | .path("/information") 36 | .query("name=Miku") 37 | .method("GET") 38 | .willRespondWith() 39 | .headers(headers) 40 | .status(200) 41 | .body("{\n" + 42 | " \"salary\": 45000,\n" + 43 | " \"name\": \"Hatsune Miku\",\n" + 44 | " \"nationality\": \"Japan\",\n" + 45 | " \"contact\": {\n" + 46 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 47 | " \"Phone Number\": \"9090950\"\n" + 48 | " }\n" + 49 | "}") 50 | 51 | .toPact(); 52 | } 53 | 54 | @Override 55 | protected String providerName() { 56 | return "ExampleProvider"; 57 | } 58 | 59 | @Override 60 | protected String consumerName() { 61 | return "BaseConsumer"; 62 | } 63 | 64 | @Override 65 | protected void runTest(MockServer mockServer, PactTestExecutionContext context) { 66 | providerService.setBackendURL(mockServer.getUrl()); 67 | Information information = providerService.getInformation(); 68 | assertEquals(information.getName(), "Hatsune Miku"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /example-consumer-miku/src/test/java/ariman/pact/consumer/PactJunitDSLJsonBodyTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.ConsumerPactBuilder; 4 | import au.com.dius.pact.consumer.PactVerificationResult; 5 | import au.com.dius.pact.consumer.dsl.DslPart; 6 | import au.com.dius.pact.consumer.dsl.PactDslJsonBody; 7 | import au.com.dius.pact.consumer.model.MockProviderConfig; 8 | import au.com.dius.pact.core.model.PactSpecVersion; 9 | import au.com.dius.pact.core.model.RequestResponsePact; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest; 20 | import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; 21 | import static org.hamcrest.CoreMatchers.instanceOf; 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertThat; 25 | 26 | 27 | @RunWith(SpringRunner.class) 28 | @SpringBootTest 29 | public class PactJunitDSLJsonBodyTest { 30 | 31 | @Autowired 32 | ProviderService providerService; 33 | 34 | private void checkResult(PactVerificationResult result) { 35 | if (result instanceof PactVerificationResult.Error) { 36 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 37 | } 38 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 39 | } 40 | 41 | @Test 42 | public void testWithPactDSLJsonBody() { 43 | Map headers = new HashMap(); 44 | headers.put("Content-Type", "application/json;charset=UTF-8"); 45 | 46 | DslPart body = new PactDslJsonBody() 47 | .numberType("salary", 45000) 48 | .stringType("name", "Hatsune Miku") 49 | .stringType("nationality", "Japan") 50 | .object("contact") 51 | .stringValue("Email", "hatsune.miku@ariman.com") 52 | .stringValue("Phone Number", "9090950") 53 | .closeObject(); 54 | 55 | RequestResponsePact pact = ConsumerPactBuilder 56 | .consumer("JunitDSLJsonBodyConsumer") 57 | .hasPactWith("ExampleProvider") 58 | .given("") 59 | .uponReceiving("Query name is Miku") 60 | .path("/information") 61 | .query("name=Miku") 62 | .method("GET") 63 | .willRespondWith() 64 | .headers(headers) 65 | .status(200) 66 | .body(body) 67 | .toPact(); 68 | 69 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 70 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 71 | providerService.setBackendURL(mockServer.getUrl()); 72 | Information information = providerService.getInformation(); 73 | assertEquals(information.getName(), "Hatsune Miku"); 74 | return null; 75 | }); 76 | 77 | checkResult(result); 78 | } 79 | 80 | @Test 81 | public void testWithLambdaDSLJsonBody() { 82 | Map headers = new HashMap(); 83 | headers.put("Content-Type", "application/json;charset=UTF-8"); 84 | 85 | DslPart body = newJsonBody((root) -> { 86 | root.numberValue("salary", 45000); 87 | root.stringValue("name", "Hatsune Miku"); 88 | root.stringValue("nationality", "Japan"); 89 | root.object("contact", (contactObject) -> { 90 | contactObject.stringMatcher("Email", ".*@ariman.com", "hatsune.miku@ariman.com"); 91 | contactObject.stringType("Phone Number", "9090950"); 92 | }); 93 | }).build(); 94 | 95 | RequestResponsePact pact = ConsumerPactBuilder 96 | .consumer("JunitDSLLambdaJsonBodyConsumer") 97 | .hasPactWith("ExampleProvider") 98 | .given("") 99 | .uponReceiving("Query name is Miku") 100 | .path("/information") 101 | .query("name=Miku") 102 | .method("GET") 103 | .willRespondWith() 104 | .headers(headers) 105 | .status(200) 106 | .body(body) 107 | .toPact(); 108 | 109 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 110 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 111 | providerService.setBackendURL(mockServer.getUrl()); 112 | Information information = providerService.getInformation(); 113 | assertEquals(information.getName(), "Hatsune Miku"); 114 | return null; 115 | }); 116 | 117 | checkResult(result); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /example-consumer-miku/src/test/java/ariman/pact/consumer/PactJunitDSLTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.ConsumerPactBuilder; 4 | import au.com.dius.pact.consumer.PactVerificationResult; 5 | import au.com.dius.pact.consumer.model.MockProviderConfig; 6 | import au.com.dius.pact.core.model.RequestResponsePact; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest; 17 | import static org.hamcrest.CoreMatchers.instanceOf; 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertThat; 21 | 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest 24 | public class PactJunitDSLTest { 25 | 26 | @Autowired 27 | ProviderService providerService; 28 | 29 | private void checkResult(PactVerificationResult result) { 30 | if (result instanceof PactVerificationResult.Error) { 31 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 32 | } 33 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 34 | } 35 | 36 | @Test 37 | public void testPact1() { 38 | Map headers = new HashMap(); 39 | headers.put("Content-Type", "application/json;charset=UTF-8"); 40 | 41 | RequestResponsePact pact = ConsumerPactBuilder 42 | .consumer("JunitDSLConsumer1") 43 | .hasPactWith("ExampleProvider") 44 | .given("") 45 | .uponReceiving("Query name is Miku") 46 | .path("/information") 47 | .query("name=Miku") 48 | .method("GET") 49 | .willRespondWith() 50 | .headers(headers) 51 | .status(200) 52 | .body("{\n" + 53 | " \"salary\": 45000,\n" + 54 | " \"name\": \"Hatsune Miku\",\n" + 55 | " \"nationality\": \"Japan\",\n" + 56 | " \"contact\": {\n" + 57 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 58 | " \"Phone Number\": \"9090950\"\n" + 59 | " }\n" + 60 | "}") 61 | .toPact(); 62 | 63 | MockProviderConfig config = MockProviderConfig.createDefault(); 64 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 65 | providerService.setBackendURL(mockServer.getUrl(), "Miku"); 66 | Information information = providerService.getInformation(); 67 | assertEquals(information.getName(), "Hatsune Miku"); 68 | return null; 69 | }); 70 | 71 | checkResult(result); 72 | } 73 | 74 | @Test 75 | public void testPact2() { 76 | Map headers = new HashMap(); 77 | headers.put("Content-Type", "application/json;charset=UTF-8"); 78 | 79 | RequestResponsePact pact = ConsumerPactBuilder 80 | .consumer("JunitDSLConsumer2") 81 | .hasPactWith("ExampleProvider") 82 | .given("") 83 | .uponReceiving("Query name is Nanoha") 84 | .path("/information") 85 | .query("name=Nanoha") 86 | .method("GET") 87 | .willRespondWith() 88 | .headers(headers) 89 | .status(200) 90 | .body("{\n" + 91 | " \"salary\": 80000,\n" + 92 | " \"name\": \"Takamachi Nanoha\",\n" + 93 | " \"nationality\": \"Japan\",\n" + 94 | " \"contact\": {\n" + 95 | " \"Email\": \"takamachi.nanoha@ariman.com\",\n" + 96 | " \"Phone Number\": \"9090940\"\n" + 97 | " }\n" + 98 | "}") 99 | .toPact(); 100 | 101 | MockProviderConfig config = MockProviderConfig.createDefault(); 102 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 103 | providerService.setBackendURL(mockServer.getUrl(), "Nanoha"); 104 | Information information = providerService.getInformation(); 105 | assertEquals(information.getName(), "Takamachi Nanoha"); 106 | return null; 107 | }); 108 | 109 | checkResult(result); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /example-consumer-miku/src/test/java/ariman/pact/consumer/PactJunitRuleMultipleInteractionsTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.dsl.PactDslWithProvider; 4 | import au.com.dius.pact.consumer.junit.PactProviderRule; 5 | import au.com.dius.pact.consumer.junit.PactVerification; 6 | import au.com.dius.pact.core.model.RequestResponsePact; 7 | import au.com.dius.pact.core.model.annotations.Pact; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class PactJunitRuleMultipleInteractionsTest { 23 | 24 | @Autowired 25 | ProviderService providerService; 26 | 27 | @Rule 28 | public PactProviderRule mockProvider = new PactProviderRule("ExampleProvider",this); 29 | 30 | @Pact(consumer="JunitRuleMultipleInteractionsConsumer") 31 | public RequestResponsePact createPact(PactDslWithProvider builder) { 32 | Map headers = new HashMap(); 33 | headers.put("Content-Type", "application/json;charset=UTF-8"); 34 | 35 | return builder 36 | .given("") 37 | .uponReceiving("Miku") 38 | .path("/information") 39 | .query("name=Miku") 40 | .method("GET") 41 | .willRespondWith() 42 | .headers(headers) 43 | .status(200) 44 | .body("{\n" + 45 | " \"salary\": 45000,\n" + 46 | " \"name\": \"Hatsune Miku\",\n" + 47 | " \"nationality\": \"Japan\",\n" + 48 | " \"contact\": {\n" + 49 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 50 | " \"Phone Number\": \"9090950\"\n" + 51 | " }\n" + 52 | "}") 53 | .given("") 54 | .uponReceiving("Nanoha") 55 | .path("/information") 56 | .query("name=Nanoha") 57 | .method("GET") 58 | .willRespondWith() 59 | .headers(headers) 60 | .status(200) 61 | .body("{\n" + 62 | " \"salary\": 80000,\n" + 63 | " \"name\": \"Takamachi Nanoha\",\n" + 64 | " \"nationality\": \"Japan\",\n" + 65 | " \"contact\": {\n" + 66 | " \"Email\": \"takamachi.nanoha@ariman.com\",\n" + 67 | " \"Phone Number\": \"9090940\"\n" + 68 | " }\n" + 69 | "}") 70 | .toPact(); 71 | } 72 | 73 | @Test 74 | @PactVerification() 75 | public void runTest() { 76 | providerService.setBackendURL(mockProvider.getUrl()); 77 | Information information = providerService.getInformation(); 78 | assertEquals(information.getName(), "Hatsune Miku"); 79 | 80 | providerService.setBackendURL(mockProvider.getUrl(), "Nanoha"); 81 | information = providerService.getInformation(); 82 | assertEquals(information.getName(), "Takamachi Nanoha"); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example-consumer-miku/src/test/java/ariman/pact/consumer/PactJunitRuleTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.dsl.PactDslWithProvider; 4 | import au.com.dius.pact.consumer.junit.PactProviderRule; 5 | import au.com.dius.pact.consumer.junit.PactVerification; 6 | import au.com.dius.pact.core.model.RequestResponsePact; 7 | import au.com.dius.pact.core.model.annotations.Pact; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class PactJunitRuleTest { 23 | 24 | @Autowired 25 | ProviderService providerService; 26 | 27 | @Rule 28 | public PactProviderRule mockProvider = new PactProviderRule("ExampleProvider", this); 29 | 30 | @Pact(consumer = "JunitRuleConsumer") 31 | public RequestResponsePact createPact(PactDslWithProvider builder) { 32 | Map headers = new HashMap(); 33 | headers.put("Content-Type", "application/json;charset=UTF-8"); 34 | 35 | return builder 36 | .given("") 37 | .uponReceiving("Pact JVM example Pact interaction") 38 | .path("/information") 39 | .query("name=Miku") 40 | .method("GET") 41 | .willRespondWith() 42 | .headers(headers) 43 | .status(200) 44 | .body("{\n" + 45 | " \"salary\": 45000,\n" + 46 | " \"name\": \"Hatsune Miku\",\n" + 47 | " \"nationality\": \"Japan\",\n" + 48 | " \"contact\": {\n" + 49 | " \"Email\": \"hatsune.miku@ariman.com\",\n" + 50 | " \"Phone Number\": \"9090950\"\n" + 51 | " }\n" + 52 | "}") 53 | .toPact(); 54 | } 55 | 56 | @Test 57 | @PactVerification 58 | public void runTest() { 59 | providerService.setBackendURL(mockProvider.getUrl()); 60 | Information information = providerService.getInformation(); 61 | assertEquals(information.getName(), "Hatsune Miku"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example-consumer-nanoha/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8082 2 | spring.thymeleaf.mode=HTML -------------------------------------------------------------------------------- /example-consumer-nanoha/gradle.properties: -------------------------------------------------------------------------------- 1 | thymeleaf.version=3.0.8.RELEASE 2 | thymeleaf-layout-dialect.version=2.2.2 3 | -------------------------------------------------------------------------------- /example-consumer-nanoha/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example-consumer-nanoha' 2 | project.version = '1.0.0' 3 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/java/ariman/pact/consumer/Application.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/java/ariman/pact/consumer/Information.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Information { 7 | private Integer salary; 8 | private String name; 9 | private String nationality; 10 | private Map contact = new HashMap(); 11 | 12 | public Integer getSalary() { 13 | return salary; 14 | } 15 | 16 | public void setSalary(Integer salary) { 17 | this.salary = salary; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public Map getContact() { 29 | return contact; 30 | } 31 | 32 | public void setContact(Map contact) { 33 | this.contact = contact; 34 | } 35 | 36 | public String getNationality() { 37 | return nationality; 38 | } 39 | 40 | public void setNationality(String nationality) { 41 | this.nationality = nationality; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/java/ariman/pact/consumer/InformationController.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | @Controller 9 | public class InformationController { 10 | 11 | @Autowired 12 | private ProviderService providerService; 13 | 14 | @RequestMapping("/nanoha") 15 | public String miku(Model model) { 16 | Information information = providerService.getInformation(); 17 | model.addAttribute("name", information.getName()); 18 | model.addAttribute("nationality", information.getNationality()); 19 | model.addAttribute("mail", information.getContact().get("Email")); 20 | model.addAttribute("phone", information.getContact().get("Phone Number")); 21 | 22 | return "nanoha"; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/java/ariman/pact/consumer/ProviderService.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.web.client.RestTemplate; 5 | 6 | @Service 7 | public class ProviderService { 8 | 9 | private String backendURL = "http://localhost:8080/information?name=Nanoha"; 10 | 11 | public String getBackendURL() { 12 | return this.backendURL; 13 | } 14 | 15 | public void setBackendURL(String URLBase) { 16 | this.backendURL = URLBase+"/information?name=Nanoha"; 17 | } 18 | public void setBackendURL(String URLBase, String name) { 19 | this.backendURL = URLBase+"/information?name="+name; 20 | } 21 | 22 | public Information getInformation() { 23 | RestTemplate restTemplate = new RestTemplate(); 24 | Information information = restTemplate.getForObject(getBackendURL(), Information.class); 25 | 26 | return information; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/resources/static/css/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-secondary, 14 | .btn-secondary:hover, 15 | .btn-secondary:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritance from `body` */ 18 | background-color: #fff; 19 | border: .05rem solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | background-color: #333; 31 | } 32 | body { 33 | color: #fff; 34 | text-align: center; 35 | text-shadow: 0 .05rem .1rem rgba(0,0,0,.5); 36 | } 37 | 38 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 39 | .site-wrapper { 40 | display: table; 41 | width: 100%; 42 | height: 100%; /* For at least Firefox */ 43 | min-height: 100%; 44 | box-shadow: inset 0 0 5rem rgba(0,0,0,.5); 45 | } 46 | .site-wrapper-inner { 47 | display: table-cell; 48 | vertical-align: top; 49 | } 50 | .cover-container { 51 | margin-right: auto; 52 | margin-left: auto; 53 | } 54 | 55 | /* Padding for spacing */ 56 | .inner { 57 | padding: 2rem; 58 | } 59 | 60 | 61 | /* 62 | * Header 63 | */ 64 | 65 | .masthead { 66 | margin-bottom: 2rem; 67 | } 68 | 69 | .masthead-brand { 70 | margin-bottom: 0; 71 | } 72 | 73 | .nav-masthead .nav-link { 74 | padding: .25rem 0; 75 | font-weight: 700; 76 | color: rgba(255,255,255,.5); 77 | background-color: transparent; 78 | border-bottom: .25rem solid transparent; 79 | } 80 | 81 | .nav-masthead .nav-link:hover, 82 | .nav-masthead .nav-link:focus { 83 | border-bottom-color: rgba(255,255,255,.25); 84 | } 85 | 86 | .nav-masthead .nav-link + .nav-link { 87 | margin-left: 1rem; 88 | } 89 | 90 | .nav-masthead .active { 91 | color: #fff; 92 | border-bottom-color: #fff; 93 | } 94 | 95 | @media (min-width: 48em) { 96 | .masthead-brand { 97 | float: left; 98 | } 99 | .nav-masthead { 100 | float: right; 101 | } 102 | } 103 | 104 | 105 | /* 106 | * Cover 107 | */ 108 | 109 | .cover { 110 | padding: 0 1.5rem; 111 | } 112 | .cover .btn-lg { 113 | padding: .75rem 1.25rem; 114 | font-weight: 700; 115 | } 116 | 117 | 118 | /* 119 | * Footer 120 | */ 121 | 122 | .mastfoot { 123 | color: rgba(255,255,255,.5); 124 | } 125 | 126 | 127 | /* 128 | * Affix and center 129 | */ 130 | 131 | @media (min-width: 40em) { 132 | /* Pull out the header and footer */ 133 | .masthead { 134 | position: fixed; 135 | top: 0; 136 | } 137 | .mastfoot { 138 | position: fixed; 139 | bottom: 0; 140 | } 141 | 142 | /* Start the vertical centering */ 143 | .site-wrapper-inner { 144 | vertical-align: middle; 145 | } 146 | 147 | /* Handle the widths */ 148 | .masthead, 149 | .mastfoot, 150 | .cover-container { 151 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 152 | } 153 | } 154 | 155 | @media (min-width: 62em) { 156 | .masthead, 157 | .mastfoot, 158 | .cover-container { 159 | width: 42rem; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/resources/static/img/nanoha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/example-consumer-nanoha/src/main/resources/static/img/nanoha.png -------------------------------------------------------------------------------- /example-consumer-nanoha/src/main/resources/templates/nanoha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Cover Template for Bootstrap 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 |

Ariman

31 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |

47 |

48 |

49 |

50 |

51 |
52 |
53 | 54 |
55 |
56 |

Example Page for Pact JVM.

57 |
58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /example-consumer-nanoha/src/test/java/ariman/pact/consumer/NationalityPactTest.java: -------------------------------------------------------------------------------- 1 | package ariman.pact.consumer; 2 | 3 | import au.com.dius.pact.consumer.ConsumerPactBuilder; 4 | import au.com.dius.pact.consumer.PactVerificationResult; 5 | import au.com.dius.pact.consumer.dsl.DslPart; 6 | import au.com.dius.pact.consumer.model.MockProviderConfig; 7 | import au.com.dius.pact.core.model.PactSpecVersion; 8 | import au.com.dius.pact.core.model.RequestResponsePact; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest; 19 | import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody; 20 | import static org.hamcrest.CoreMatchers.instanceOf; 21 | import static org.hamcrest.CoreMatchers.is; 22 | import static org.junit.Assert.*; 23 | 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest 26 | public class NationalityPactTest { 27 | 28 | @Autowired 29 | ProviderService providerService; 30 | 31 | private void checkResult(PactVerificationResult result) { 32 | if (result instanceof PactVerificationResult.Error) { 33 | throw new RuntimeException(((PactVerificationResult.Error) result).getError()); 34 | } 35 | assertThat(result, is(instanceOf(PactVerificationResult.Ok.class))); 36 | } 37 | 38 | @Test 39 | public void testWithNationality() { 40 | Map headers = new HashMap(); 41 | headers.put("Content-Type", "application/json;charset=UTF-8"); 42 | 43 | DslPart body = newJsonBody((root) -> { 44 | root.numberType("salary"); 45 | root.stringValue("name", "Takamachi Nanoha"); 46 | root.stringValue("nationality", "Japan"); 47 | root.object("contact", (contactObject) -> { 48 | contactObject.stringMatcher("Email", ".*@ariman.com", "takamachi.nanoha@ariman.com"); 49 | contactObject.stringType("Phone Number", "9090940"); 50 | }); 51 | }).build(); 52 | 53 | RequestResponsePact pact = ConsumerPactBuilder 54 | .consumer("ConsumerNanohaWithNationality") 55 | .hasPactWith("ExampleProvider") 56 | .given("") 57 | .uponReceiving("Query name is Nanoha") 58 | .path("/information") 59 | .query("name=Nanoha") 60 | .method("GET") 61 | .willRespondWith() 62 | .headers(headers) 63 | .status(200) 64 | .body(body) 65 | .toPact(); 66 | 67 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 68 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 69 | providerService.setBackendURL(mockServer.getUrl()); 70 | Information information = providerService.getInformation(); 71 | assertEquals(information.getName(), "Takamachi Nanoha"); 72 | assertEquals(information.getNationality(), "Japan"); 73 | return null; 74 | }); 75 | 76 | checkResult(result); 77 | } 78 | 79 | @Test 80 | public void testNoNationality() { 81 | Map headers = new HashMap(); 82 | headers.put("Content-Type", "application/json;charset=UTF-8"); 83 | 84 | DslPart body = newJsonBody((root) -> { 85 | root.numberType("salary"); 86 | root.stringValue("name", "Takamachi Nanoha"); 87 | root.stringValue("nationality", null); 88 | root.object("contact", (contactObject) -> { 89 | contactObject.stringMatcher("Email", ".*@ariman.com", "takamachi.nanoha@ariman.com"); 90 | contactObject.stringType("Phone Number", "9090940"); 91 | }); 92 | }).build(); 93 | 94 | RequestResponsePact pact = ConsumerPactBuilder 95 | .consumer("ConsumerNanohaNoNationality") 96 | .hasPactWith("ExampleProvider") 97 | .given("No nationality") 98 | .uponReceiving("Query name is Nanoha") 99 | .path("/information") 100 | .query("name=Nanoha") 101 | .method("GET") 102 | .willRespondWith() 103 | .headers(headers) 104 | .status(200) 105 | .body(body) 106 | .toPact(); 107 | 108 | MockProviderConfig config = MockProviderConfig.createDefault(PactSpecVersion.V3); 109 | PactVerificationResult result = runConsumerTest(pact, config, (mockServer, context) -> { 110 | providerService.setBackendURL(mockServer.getUrl()); 111 | Information information = providerService.getInformation(); 112 | assertEquals(information.getName(), "Takamachi Nanoha"); 113 | assertNull(information.getNationality()); 114 | return null; 115 | }); 116 | 117 | checkResult(result); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/BaseConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "BaseConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Pact JVM example Pact interaction", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset=UTF-8" 24 | }, 25 | "body": { 26 | "salary": 45000, 27 | "name": "Hatsune Miku", 28 | "contact": { 29 | "Email": "hatsune.miku@ariman.com", 30 | "Phone Number": "9090950" 31 | } 32 | } 33 | }, 34 | "providerStates": [ 35 | { 36 | "name": "Base State" 37 | } 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "pact-specification": { 43 | "version": "3.0.0" 44 | }, 45 | "pact-jvm": { 46 | "version": "3.5.9" 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitDSLConsumer1-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitDSLConsumer1" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Query name is Miku", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": "name=Miku" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers": { 19 | "Content-Type": "application/json;charset=UTF-8" 20 | }, 21 | "body": { 22 | "salary": 45000, 23 | "name": "Hatsune Miku", 24 | "contact": { 25 | "Email": "hatsune.miku@ariman.com", 26 | "Phone Number": "9090950" 27 | } 28 | } 29 | }, 30 | "providerState": "Junit DSL State" 31 | }, 32 | { 33 | "description": "Query name is Miku", 34 | "request": { 35 | "method": "GET", 36 | "path": "/information", 37 | "query": "name=Miku" 38 | }, 39 | "response": { 40 | "status": 200, 41 | "headers": { 42 | "Content-Type": "application/json;charset=UTF-8" 43 | }, 44 | "body": { 45 | "salary": 45000, 46 | "name": "Hatsune Miku", 47 | "contact": { 48 | "Email": "hatsune.miku@ariman.com", 49 | "Phone Number": "9090950" 50 | } 51 | } 52 | }, 53 | "providerState": "Test state" 54 | } 55 | ], 56 | "metadata": { 57 | "pact-specification": { 58 | "version": "2.0.0" 59 | }, 60 | "pact-jvm": { 61 | "version": "3.5.9" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitDSLConsumer2-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitDSLConsumer2" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Query name is Nanoha", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": "name=Nanoha" 15 | }, 16 | "response": { 17 | "status": 200, 18 | "headers": { 19 | "Content-Type": "application/json;charset=UTF-8" 20 | }, 21 | "body": { 22 | "salary": 0, 23 | "name": "Nanoha", 24 | "contact": null 25 | } 26 | }, 27 | "providerState": "Junit DSL State" 28 | }, 29 | { 30 | "description": "Query name is Nanoha", 31 | "request": { 32 | "method": "GET", 33 | "path": "/information", 34 | "query": "name=Nanoha" 35 | }, 36 | "response": { 37 | "status": 200, 38 | "headers": { 39 | "Content-Type": "application/json;charset=UTF-8" 40 | }, 41 | "body": { 42 | "salary": 0, 43 | "name": "Nanoha", 44 | "contact": null 45 | } 46 | } 47 | } 48 | ], 49 | "metadata": { 50 | "pact-specification": { 51 | "version": "2.0.0" 52 | }, 53 | "pact-jvm": { 54 | "version": "3.5.9" 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitDSLJsonBodyConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitDSLJsonBodyConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Query name is Miku", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset=UTF-8" 24 | }, 25 | "body": { 26 | "contact": { 27 | "Email": "hatsune.miku@ariman.com", 28 | "Phone Number": "9090950" 29 | }, 30 | "name": "Hatsune Miku", 31 | "salary": 45000 32 | }, 33 | "matchingRules": { 34 | "body": { 35 | "$.salary": { 36 | "matchers": [ 37 | { 38 | "match": "number" 39 | } 40 | ], 41 | "combine": "AND" 42 | }, 43 | "$.name": { 44 | "matchers": [ 45 | { 46 | "match": "type" 47 | } 48 | ], 49 | "combine": "AND" 50 | } 51 | } 52 | } 53 | }, 54 | "providerStates": [ 55 | { 56 | "name": "Junit DSL State" 57 | } 58 | ] 59 | }, 60 | { 61 | "description": "Query name is Miku", 62 | "request": { 63 | "method": "GET", 64 | "path": "/information", 65 | "query": { 66 | "name": [ 67 | "Miku" 68 | ] 69 | } 70 | }, 71 | "response": { 72 | "status": 200, 73 | "headers": { 74 | "Content-Type": "application/json;charset=UTF-8" 75 | }, 76 | "body": { 77 | "contact": { 78 | "Email": "hatsune.miku@ariman.com", 79 | "Phone Number": "9090950" 80 | }, 81 | "name": "Hatsune Miku", 82 | "salary": 45000 83 | }, 84 | "matchingRules": { 85 | "body": { 86 | "$.salary": { 87 | "matchers": [ 88 | { 89 | "match": "number" 90 | } 91 | ], 92 | "combine": "AND" 93 | }, 94 | "$.name": { 95 | "matchers": [ 96 | { 97 | "match": "type" 98 | } 99 | ], 100 | "combine": "AND" 101 | } 102 | } 103 | } 104 | }, 105 | "providerStates": [ 106 | { 107 | "name": "Test state" 108 | } 109 | ] 110 | } 111 | ], 112 | "metadata": { 113 | "pact-specification": { 114 | "version": "3.0.0" 115 | }, 116 | "pact-jvm": { 117 | "version": "3.5.9" 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitDSLLambdaJsonBodyConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitDSLLambdaJsonBodyConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Query name is Miku", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset=UTF-8" 24 | }, 25 | "body": { 26 | "contact": { 27 | "Email": "hatsune.miku@ariman.com", 28 | "Phone Number": "9090950" 29 | }, 30 | "name": "Hatsune Miku", 31 | "salary": 45000 32 | }, 33 | "matchingRules": { 34 | "body": { 35 | "$.contact.Email": { 36 | "matchers": [ 37 | { 38 | "match": "regex", 39 | "regex": ".*@ariman.com" 40 | } 41 | ], 42 | "combine": "AND" 43 | }, 44 | "$.contact['Phone Number']": { 45 | "matchers": [ 46 | { 47 | "match": "type" 48 | } 49 | ], 50 | "combine": "AND" 51 | } 52 | } 53 | } 54 | }, 55 | "providerStates": [ 56 | { 57 | "name": "Junit DSL State" 58 | } 59 | ] 60 | }, 61 | { 62 | "description": "Query name is Miku", 63 | "request": { 64 | "method": "GET", 65 | "path": "/information", 66 | "query": { 67 | "name": [ 68 | "Miku" 69 | ] 70 | } 71 | }, 72 | "response": { 73 | "status": 200, 74 | "headers": { 75 | "Content-Type": "application/json;charset=UTF-8" 76 | }, 77 | "body": { 78 | "contact": { 79 | "Email": "hatsune.miku@ariman.com", 80 | "Phone Number": "9090950" 81 | }, 82 | "name": "Hatsune Miku", 83 | "salary": 45000 84 | }, 85 | "matchingRules": { 86 | "body": { 87 | "$.contact.Email": { 88 | "matchers": [ 89 | { 90 | "match": "regex", 91 | "regex": ".*@ariman.com" 92 | } 93 | ], 94 | "combine": "AND" 95 | }, 96 | "$.contact['Phone Number']": { 97 | "matchers": [ 98 | { 99 | "match": "type" 100 | } 101 | ], 102 | "combine": "AND" 103 | } 104 | } 105 | } 106 | }, 107 | "providerStates": [ 108 | { 109 | "name": "Test state" 110 | } 111 | ] 112 | } 113 | ], 114 | "metadata": { 115 | "pact-specification": { 116 | "version": "3.0.0" 117 | }, 118 | "pact-jvm": { 119 | "version": "3.5.9" 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitRuleConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitRuleConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Pact JVM example Pact interaction", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset=UTF-8" 24 | }, 25 | "body": { 26 | "salary": 45000, 27 | "name": "Hatsune Miku", 28 | "contact": { 29 | "Email": "hatsune.miku@ariman.com", 30 | "Phone Number": "9090950" 31 | } 32 | } 33 | }, 34 | "providerStates": [ 35 | { 36 | "name": "Junit Rule State" 37 | } 38 | ] 39 | }, 40 | { 41 | "description": "Pact JVM example Pact interaction", 42 | "request": { 43 | "method": "GET", 44 | "path": "/information", 45 | "query": { 46 | "name": [ 47 | "Miku" 48 | ] 49 | } 50 | }, 51 | "response": { 52 | "status": 200, 53 | "headers": { 54 | "Content-Type": "application/json;charset=UTF-8" 55 | }, 56 | "body": { 57 | "salary": 45000, 58 | "name": "Hatsune Miku", 59 | "contact": { 60 | "Email": "hatsune.miku@ariman.com", 61 | "Phone Number": "9090950" 62 | } 63 | } 64 | }, 65 | "providerStates": [ 66 | { 67 | "name": "" 68 | } 69 | ] 70 | } 71 | ], 72 | "metadata": { 73 | "pact-specification": { 74 | "version": "3.0.0" 75 | }, 76 | "pact-jvm": { 77 | "version": "3.5.9" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /example-consumer-nanoha/target/pacts/JunitRuleMultipleInteractionsConsumer-ExampleProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": { 3 | "name": "ExampleProvider" 4 | }, 5 | "consumer": { 6 | "name": "JunitRuleMultipleInteractionsConsumer" 7 | }, 8 | "interactions": [ 9 | { 10 | "description": "Miku", 11 | "request": { 12 | "method": "GET", 13 | "path": "/information", 14 | "query": { 15 | "name": [ 16 | "Miku" 17 | ] 18 | } 19 | }, 20 | "response": { 21 | "status": 200, 22 | "headers": { 23 | "Content-Type": "application/json;charset=UTF-8" 24 | }, 25 | "body": { 26 | "salary": 45000, 27 | "name": "Hatsune Miku", 28 | "contact": { 29 | "Email": "hatsune.miku@ariman.com", 30 | "Phone Number": "9090950" 31 | } 32 | } 33 | }, 34 | "providerStates": [ 35 | { 36 | "name": "Junit Rule State" 37 | } 38 | ] 39 | }, 40 | { 41 | "description": "Nanoha", 42 | "request": { 43 | "method": "GET", 44 | "path": "/information", 45 | "query": { 46 | "name": [ 47 | "Nanoha" 48 | ] 49 | } 50 | }, 51 | "response": { 52 | "status": 200, 53 | "headers": { 54 | "Content-Type": "application/json;charset=UTF-8" 55 | }, 56 | "body": { 57 | "salary": 0, 58 | "name": "Nanoha", 59 | "contact": null 60 | } 61 | }, 62 | "providerStates": [ 63 | { 64 | "name": "Junit Rule State" 65 | } 66 | ] 67 | }, 68 | { 69 | "description": "Miku", 70 | "request": { 71 | "method": "GET", 72 | "path": "/information", 73 | "query": { 74 | "name": [ 75 | "Miku" 76 | ] 77 | } 78 | }, 79 | "response": { 80 | "status": 200, 81 | "headers": { 82 | "Content-Type": "application/json;charset=UTF-8" 83 | }, 84 | "body": { 85 | "salary": 45000, 86 | "name": "Hatsune Miku", 87 | "contact": { 88 | "Email": "hatsune.miku@ariman.com", 89 | "Phone Number": "9090950" 90 | } 91 | } 92 | }, 93 | "providerStates": [ 94 | { 95 | "name": "State Miku" 96 | } 97 | ] 98 | }, 99 | { 100 | "description": "Nanoha", 101 | "request": { 102 | "method": "GET", 103 | "path": "/information", 104 | "query": { 105 | "name": [ 106 | "Nanoha" 107 | ] 108 | } 109 | }, 110 | "response": { 111 | "status": 200, 112 | "headers": { 113 | "Content-Type": "application/json;charset=UTF-8" 114 | }, 115 | "body": { 116 | "salary": 0, 117 | "name": "Nanoha", 118 | "contact": null 119 | } 120 | }, 121 | "providerStates": [ 122 | { 123 | "name": "State Nanoha" 124 | } 125 | ] 126 | } 127 | ], 128 | "metadata": { 129 | "pact-specification": { 130 | "version": "3.0.0" 131 | }, 132 | "pact-jvm": { 133 | "version": "3.5.9" 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /example-provider/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | server: 3 | address: localhost 4 | port: 8080 5 | --- 6 | spring: 7 | profiles: default 8 | --- 9 | spring: 10 | profiles: pact 11 | 12 | -------------------------------------------------------------------------------- /example-provider/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example-provider' 2 | project.version = '1.0.0' 3 | 4 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/Application.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/Information.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | import provider.ulti.Nationality; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class Information { 9 | private Integer salary; 10 | private String name; 11 | private String nationality; 12 | private Map contact = new HashMap(); 13 | 14 | public Integer getSalary() { 15 | return salary; 16 | } 17 | 18 | public void setSalary(Integer salary) { 19 | this.salary = salary; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public Map getContact() { 31 | return contact; 32 | } 33 | 34 | public void setContact(Map contact) { 35 | this.contact = contact; 36 | } 37 | 38 | public String getNationality() { 39 | return nationality; 40 | } 41 | 42 | public void setNationality(String nationality) { 43 | this.nationality = nationality; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/InformationController.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.web.bind.annotation.*; 8 | import provider.ulti.Nationality; 9 | 10 | @RestController 11 | public class InformationController { 12 | 13 | private Information information = new Information(); 14 | 15 | @RequestMapping("/information") 16 | public Information information(@RequestParam(value="name", defaultValue="Miku") String name) { 17 | if (name.equals("Miku")) { 18 | HashMap contact = new HashMap(); 19 | contact.put("Email", "hatsune.miku@ariman.com"); 20 | contact.put("Phone Number", "9090950"); 21 | information.setNationality(Nationality.getNationality()); 22 | information.setContact(contact); 23 | information.setName("Hatsune Miku"); 24 | information.setSalary(45000); 25 | 26 | } else if (name.equals("Nanoha")) { 27 | HashMap contact = new HashMap(); 28 | contact.put("Email", "takamachi.nanoha@ariman.com"); 29 | contact.put("Phone Number", "9090940"); 30 | information.setNationality(Nationality.getNationality()); 31 | information.setContact(contact); 32 | information.setName("Takamachi Nanoha"); 33 | information.setSalary(80000); 34 | 35 | } else { 36 | information.setNationality(Nationality.getNationality()); 37 | information.setContact(null); 38 | information.setName(name); 39 | information.setSalary(0); 40 | } 41 | 42 | return information; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/PactController.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.web.bind.annotation.*; 5 | import provider.ulti.Nationality; 6 | 7 | @Profile("pact") 8 | @RestController 9 | public class PactController { 10 | 11 | @RequestMapping(value = "/pactStateChange", method = RequestMethod.POST) 12 | public PactStateChangeResponseDTO providerState(@RequestBody PactState body) { 13 | switch (body.getState()) { 14 | case "No nationality": 15 | Nationality.setNationality(null); 16 | System.out.println("Pact State Change >> remove nationality ..."); 17 | break; 18 | case "Default nationality": 19 | Nationality.setNationality("Japan"); 20 | System.out.println("Pact Sate Change >> set default nationality ..."); 21 | break; 22 | } 23 | 24 | // This response is not mandatory for Pact state change. The only reason is the current Pact-JVM v4.0.3 does 25 | // check the stateChange request's response, more exactly, checking the response's Content-Type, couldn't be 26 | // null, so it MUST return something here. 27 | PactStateChangeResponseDTO pactStateChangeResponse = new PactStateChangeResponseDTO(); 28 | pactStateChangeResponse.setState(body.getState()); 29 | 30 | return pactStateChangeResponse; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/PactState.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | public class PactState { 4 | private String state; 5 | 6 | public String getState() { 7 | return state; 8 | } 9 | 10 | public void setState(String state) { 11 | this.state = state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/PactStateChangeResponseDTO.java: -------------------------------------------------------------------------------- 1 | package provider; 2 | 3 | public class PactStateChangeResponseDTO { 4 | private String state; 5 | 6 | public String getState() { 7 | return state; 8 | } 9 | 10 | public void setState(String state) { 11 | this.state = state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example-provider/src/main/java/provider/ulti/Nationality.java: -------------------------------------------------------------------------------- 1 | package provider.ulti; 2 | 3 | /** 4 | * Created by Biao on 14/12/2017. 5 | */ 6 | public class Nationality { 7 | private static String nationality = "Japan"; 8 | 9 | public static String getNationality() { 10 | return nationality; 11 | } 12 | 13 | public static void setNationality(String nationality) { 14 | Nationality.nationality = nationality; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | mybrokerUrl=https://ariman.pact.dius.com.au 2 | mybrokerUser=bdYdtbx2qrRHsHfmYDOar8ecaXX0haE 3 | mybrokerPassword=wuKYeBiLtmhKqR5ODqpmzZ7iVcDVt 4 | # To publish verification results on the pact broker 5 | pact.verifier.publishResults=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 27 14:40:54 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /screenshot/consumer.miku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/screenshot/consumer.miku.png -------------------------------------------------------------------------------- /screenshot/consumer.nanoha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/screenshot/consumer.nanoha.png -------------------------------------------------------------------------------- /screenshot/pact-broker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/screenshot/pact-broker.png -------------------------------------------------------------------------------- /screenshot/provider.miku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/screenshot/provider.miku.png -------------------------------------------------------------------------------- /screenshot/provider.nanoha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mikuu/Pact-JVM-Example/6a546d728f55b9b748d50b9b314bb6ec1d8d206a/screenshot/provider.nanoha.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include "example-consumer-miku", "example-consumer-nanoha", "example-provider" 2 | 3 | --------------------------------------------------------------------------------