diff --git a/pact-angular/pacts/ui-userservice.json b/pact-angular/pacts/ui-userservice.json new file mode 100644 index 0000000..e7c3b8c --- /dev/null +++ b/pact-angular/pacts/ui-userservice.json @@ -0,0 +1,78 @@ +{ + "consumer": { + "name": "ui" + }, + "provider": { + "name": "userservice" + }, + "interactions": [ + { + "description": "a request to POST a person", + "providerState": "provider accepts a new person", + "request": { + "method": "POST", + "path": "/user-service/users", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "firstName": "Arthur", + "lastName": "Dent" + } + }, + "response": { + "status": 201, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "id": 42 + }, + "matchingRules": { + "$.body": { + "match": "type" + } + } + } + }, + { + "description": "a request to PUT a person", + "providerState": "person 42 exists", + "request": { + "method": "PUT", + "path": "/user-service/users/42", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "firstName": "Zaphod", + "lastName": "Beeblebrox" + }, + "matchingRules": { + "$.body": { + "match": "type" + } + } + }, + "response": { + "status": 200, + "headers": { + }, + "body": { + "firstName": "Zaphod", + "lastName": "Beeblebrox" + }, + "matchingRules": { + "$.body": { + "match": "type" + } + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +} \ No newline at end of file diff --git a/pact-angular/src/app/user.service.pact.spec.ts b/pact-angular/src/app/user.service.pact.spec.ts index ec20ad3..8d4a3da 100644 --- a/pact-angular/src/app/user.service.pact.spec.ts +++ b/pact-angular/src/app/user.service.pact.spec.ts @@ -92,4 +92,41 @@ describe('UserService', () => { }); + describe('update()', () => { + + const expectedUser: User = { + firstName: 'Zaphod', + lastName: 'Beeblebrox' + }; + + beforeAll((done) => { + provider.addInteraction({ + state: `person 42 exists`, + uponReceiving: 'a request to PUT a person', + withRequest: { + method: 'PUT', + path: '/user-service/users/42', + body: Pact.Matchers.somethingLike(expectedUser), + headers: { + 'Content-Type': 'application/json' + } + }, + willRespondWith: { + status: 200, + body: Pact.Matchers.somethingLike(expectedUser) + } + }).then(done, error => done.fail(error)); + }); + + it('should update a Person', (done) => { + const userService: UserService = TestBed.get(UserService); + userService.update(expectedUser, 42).subscribe(response => { + done(); + }, error => { + done.fail(error); + }); + }); + + }); + }); diff --git a/pact-spring-provider/README.md b/pact-spring-provider/README.md index ab44ba8..d8fbf91 100644 --- a/pact-spring-provider/README.md +++ b/pact-spring-provider/README.md @@ -1,16 +1,15 @@ -# Consumer-Driven-Contract Test for a Spring Data Rest Provider +# Consumer-Driven-Contract Test for a Spring Boot Provider This repo contains an example of consumer-driven-contract testing for a Spring -Data REST API provider. The corresponding consumer to the contract is -implemented in the module `pact-feign-consumer`. +Boot API provider. The corresponding consumer to the contract is +implemented in the module `pact-angular`. The contract is created and verified with [Pact](https://docs.pact.io/). -## Companion Blog Post - -The Companion Blog Post to this project can be found [here](https://reflectoring.io/consumer-driven-contracts-with-pact-feign-spring-data-rest/). +Before running the build, you need to follow the instructions on the [consumer-side](../pact-angular/) +to create the consumer-driven contract file (pact file). ## Running the application -The interesting part in this code base is the class `ProviderPactVerificationTest`. +The interesting part in this code base is the class `UserControllerProviderTest`. You can run the tests with `gradlew test` on Windows or `./gradlew test` on Unix. \ No newline at end of file diff --git a/pact-spring-provider/build.gradle b/pact-spring-provider/build.gradle index ad8100d..16bce52 100644 --- a/pact-spring-provider/build.gradle +++ b/pact-spring-provider/build.gradle @@ -26,7 +26,7 @@ dependencies { compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-web') compile('com.h2database:h2:1.4.196') - testCompile('au.com.dius:pact-jvm-provider-spring_2.12:3.5.8') + testCompile('au.com.dius:pact-jvm-provider-spring_2.12:3.5.9') testCompile('junit:junit:4.12') testCompile('org.springframework.boot:spring-boot-starter-test') } diff --git a/pact-spring-provider/gradle/wrapper/gradle-wrapper.properties b/pact-spring-provider/gradle/wrapper/gradle-wrapper.properties index 90a06ce..52dd1f0 100644 --- a/pact-spring-provider/gradle/wrapper/gradle-wrapper.properties +++ b/pact-spring-provider/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip diff --git a/pact-spring-provider/src/main/java/com/example/demo/User.java b/pact-spring-provider/src/main/java/com/example/demo/User.java index ba387bd..d6a6ea4 100644 --- a/pact-spring-provider/src/main/java/com/example/demo/User.java +++ b/pact-spring-provider/src/main/java/com/example/demo/User.java @@ -44,4 +44,9 @@ public class User { public void setLastName(String lastName) { this.lastName = lastName; } + + public void updateFrom(User user){ + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + } } diff --git a/pact-spring-provider/src/main/java/com/example/demo/UserController.java b/pact-spring-provider/src/main/java/com/example/demo/UserController.java index 0b4f19c..1e0c9aa 100644 --- a/pact-spring-provider/src/main/java/com/example/demo/UserController.java +++ b/pact-spring-provider/src/main/java/com/example/demo/UserController.java @@ -1,6 +1,7 @@ package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -24,6 +25,14 @@ public class UserController { .body(new IdObject(savedUser.getId())); } + @PutMapping(path = "/user-service/users/{id}") + public ResponseEntity updateUser(@RequestBody @Valid User user, @PathVariable long id) { + User userFromDb = userRepository.findOne(id); + userFromDb.updateFrom(user); + userFromDb = userRepository.save(userFromDb); + return ResponseEntity.ok(userFromDb); + } + @GetMapping(path = "/user-service/users/{id}") public ResponseEntity getUser(@PathVariable("id") Long id) { return ResponseEntity.ok(userRepository.findOne(id)); diff --git a/pact-spring-provider/src/test/java/com/example/demo/UserControllerProviderTest.java b/pact-spring-provider/src/test/java/com/example/demo/UserControllerProviderTest.java index 89dd8c1..2dc8c60 100644 --- a/pact-spring-provider/src/test/java/com/example/demo/UserControllerProviderTest.java +++ b/pact-spring-provider/src/test/java/com/example/demo/UserControllerProviderTest.java @@ -2,8 +2,7 @@ package com.example.demo; import au.com.dius.pact.provider.junit.Provider; import au.com.dius.pact.provider.junit.State; -import au.com.dius.pact.provider.junit.loader.PactBroker; -import au.com.dius.pact.provider.junit.loader.PactBrokerAuth; +import au.com.dius.pact.provider.junit.loader.PactFolder; import au.com.dius.pact.provider.junit.target.HttpTarget; import au.com.dius.pact.provider.junit.target.Target; import au.com.dius.pact.provider.junit.target.TestTarget; @@ -11,33 +10,33 @@ import au.com.dius.pact.provider.spring.SpringRestPactRunner; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; - +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; @RunWith(SpringRestPactRunner.class) @Provider("userservice") -@PactBroker(host = "adesso.pact.dius.com.au/", port = "80", protocol = "https", authentication = -@PactBrokerAuth(username = "Vm6YWrQURJ1T7mDIRiKwfexCAc4HbU", password = "aLerJwBhpEcN0Wm88Wgvs45AR9dXpc") -) +@PactFolder("../pact-angular/pacts") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = { - "server.port=8080" + "server.port=8080" }) public class UserControllerProviderTest { - @MockBean - private UserRepository userRepository; + @MockBean + private UserRepository userRepository; - @TestTarget - public final Target target = new HttpTarget(8080); + @TestTarget + public final Target target = new HttpTarget(8080); - @State("provider accepts a new person") - public void toCreatePersonState() { - User user = new User(); - user.setId(42L); - user.setFirstName("Arthur"); - user.setLastName("Dent"); - when(userRepository.save(any(User.class))).thenReturn(user); - } + @State({"provider accepts a new person", + "person 42 exists"}) + public void toCreatePersonState() { + User user = new User(); + user.setId(42L); + user.setFirstName("Arthur"); + user.setLastName("Dent"); + when(userRepository.findOne(eq(42L))).thenReturn(user); + when(userRepository.save(any(User.class))).thenReturn(user); + } } \ No newline at end of file