Compare commits

..

9 Commits

Author SHA1 Message Date
Tom Hombergs
ed45066496 updated pact-feign example to Spring Boot 2 and JUnit 5 2018-08-10 00:09:11 +02:00
Tom Hombergs
fb148fcd14 added article link 2018-06-11 22:33:27 +02:00
Tom Hombergs
a07407dc9f changed CI config to skip build on docs-only-changes 2018-06-11 22:15:15 +02:00
Tom Hombergs
5e4c962b21 Merge pull request #6 from thombergs/spring-boot-testing
Spring boot testing
2018-05-27 22:31:06 +02:00
Tom Hombergs
fe9e15b813 Update README.md 2018-03-20 21:48:25 +01:00
Tom Hombergs
9e66b87b18 Update README.md 2018-03-20 21:47:40 +01:00
Tom Hombergs
9bae6d560a Update README.md 2018-03-20 21:36:10 +01:00
Tom Hombergs
9d58b1323e Update README.md 2018-03-20 21:32:59 +01:00
Tom Hombergs
612863cb2a Update README.md 2018-03-20 21:32:10 +01:00
11 changed files with 253 additions and 87 deletions

View File

@@ -1,5 +1,11 @@
before_install: before_install:
- chmod +x gradlew - chmod +x gradlew
- |
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
then
echo "Not running CI since only docs were changed."
exit
fi
language: java language: java

View File

@@ -1,13 +1,11 @@
# Testing a Spring Boot REST API Consumer against a Contract with Spring Cloud Contract # Testing a Spring Boot REST API Consumer against a Contract with Pact
## Companion Blog Article ## Companion Blog Article
Read the [companion blog article](https://reflectoring.io/consumer-driven-contract-consumer-spring-cloud-contract/) to this repository. Read the [companion blog article](http://localhost:4000/consumer-driven-contract-feign-pact/) to this repository.
## Getting Started ## Getting Started
* have a look at the [contract](/src/test/resources/contracts) * have a look at the [feign client](src/main/java/io/reflectoring/UserClient.java)
* have a look at the [feign client](/src/main/java/io/reflectoring/UserClient.java) * have a look at the [consumer test](src/test/java/io/reflectoring/UserServiceConsumerTest.java)
* have a look at the [consumer test](/src/test/java/io/reflectoring/UserClientTest.java) * run `./gradlew build` in this project to create a pact and run the consumer test
* run `./gradlew publishToMavenLocal` in the [producer project](../spring-cloud-contract-provider) * afterwards, find the pact contract file in the folder `target/pacts`
to create a provider stubs
* run `./gradlew build` in this project to verify the feign client against the stub

View File

@@ -1,5 +1,3 @@
apply plugin: 'org.springframework.boot'
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
@@ -10,18 +8,43 @@ buildscript {
} }
} }
plugins {
id "au.com.dius.pact" version "3.5.20"
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
version '1.0.0.SNAPSHOT'
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
} }
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springcloud_version}"
}
}
dependencies { dependencies {
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}") compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}") compile('org.springframework.boot:spring-boot-starter-web')
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE") compile('org.springframework.cloud:spring-cloud-starter-openfeign')
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
compile('com.h2database:h2:1.4.196') compile('com.h2database:h2:1.4.196')
testCompile('org.codehaus.groovy:groovy-all:2.4.6') testCompile('org.codehaus.groovy:groovy-all:2.4.6')
testCompile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.2") testCompile("au.com.dius:pact-jvm-consumer-junit5_2.12:${pact_version}")
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}") testCompile('org.springframework.boot:spring-boot-starter-test')
}
pact {
publish {
pactDirectory = 'target/pacts'
pactBrokerUrl = 'URL'
pactBrokerUsername = 'USERNAME'
pactBrokerPassword = 'PASSWORD'
}
} }

View File

@@ -1,2 +1,3 @@
springboot_version=1.5.9.RELEASE springboot_version=2.0.4.RELEASE
verifier_version=1.2.2.RELEASE springcloud_version=Finchley.SR1
pact_version=3.5.20

View File

@@ -2,10 +2,12 @@ package io.reflectoring;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication @SpringBootApplication
@EnableFeignClients @EnableFeignClients
@RibbonClient(name = "userservice", configuration = RibbonConfiguration.class)
public class ConsumerApplication { public class ConsumerApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -0,0 +1,15 @@
package io.reflectoring;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(IClientConfig config) {
return new RandomRule();
}
}

View File

@@ -1,6 +1,6 @@
package io.reflectoring; package io.reflectoring;
import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,83 +1,85 @@
package io.reflectoring; package io.reflectoring;
import au.com.dius.pact.consumer.Pact; import au.com.dius.pact.consumer.Pact;
import au.com.dius.pact.consumer.PactProviderRuleMk2;
import au.com.dius.pact.consumer.PactVerification;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody; import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.model.RequestResponsePact; import au.com.dius.pact.model.RequestResponsePact;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.*;
@RunWith(SpringRunner.class) @ExtendWith(PactConsumerTestExt.class)
@SpringBootTest(properties = { @ExtendWith(SpringExtension.class)
// overriding provider address @PactTestFor(providerName = "userservice", port = "8888")
"userservice.ribbon.listOfServers: localhost:8888" @SpringBootTest({
// overriding provider address
"userservice.ribbon.listOfServers: localhost:8888"
}) })
public class UserServiceConsumerTest { class UserServiceConsumerTest {
@Rule @Autowired
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("userservice", "localhost", 8888, this); private UserClient userClient;
@Autowired @Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
private UserClient userClient; RequestResponsePact createPersonPact(PactDslWithProvider builder) {
// @formatter:off
return builder
.given("provider accepts a new person")
.uponReceiving("a request to POST a person")
.path("/user-service/users")
.method("POST")
.willRespondWith()
.status(201)
.matchHeader("Content-Type", "application/json")
.body(new PactDslJsonBody()
.integerType("id", 42))
.toPact();
// @formatter:on
}
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient") @Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
public RequestResponsePact createPersonPact(PactDslWithProvider builder) { RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
return builder // @formatter:off
.given("provider accepts a new person") return builder
.uponReceiving("a request to POST a person") .given("person 42 exists")
.path("/user-service/users") .uponReceiving("a request to PUT a person")
.method("POST") .path("/user-service/users/42")
.willRespondWith() .method("PUT")
.status(201) .willRespondWith()
.matchHeader("Content-Type", "application/json") .status(200)
.body(new PactDslJsonBody() .matchHeader("Content-Type", "application/json")
.integerType("id", 42)) .body(new PactDslJsonBody()
.toPact(); .stringType("firstName", "Zaphod")
} .stringType("lastName", "Beeblebrox"))
.toPact();
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient") // @formatter:on
public RequestResponsePact updatePersonPact(PactDslWithProvider builder) { }
return builder
.given("person 42 exists")
.uponReceiving("a request to PUT a person")
.path("/user-service/users/42")
.method("PUT")
.willRespondWith()
.status(200)
.matchHeader("Content-Type", "application/json")
.body(new PactDslJsonBody()
.stringType("firstName", "Zaphod")
.stringType("lastName", "Beeblebrox"))
.toPact();
}
@Test @Test
@PactVerification(fragment = "createPersonPact") @PactTestFor(pactMethod = "createPersonPact")
public void verifyCreatePersonPact() { void verifyCreatePersonPact() {
User user = new User(); User user = new User();
user.setFirstName("Zaphod"); user.setFirstName("Zaphod");
user.setLastName("Beeblebrox"); user.setLastName("Beeblebrox");
IdObject id = userClient.createUser(user); IdObject id = userClient.createUser(user);
assertThat(id.getId()).isEqualTo(42); assertThat(id.getId()).isEqualTo(42);
} }
@Test @Test
@PactVerification(fragment = "updatePersonPact") @PactTestFor(pactMethod = "updatePersonPact")
public void verifyUpdatePersonPact() { void verifyUpdatePersonPact() {
User user = new User(); User user = new User();
user.setFirstName("Zaphod"); user.setFirstName("Zaphod");
user.setLastName("Beeblebrox"); user.setLastName("Beeblebrox");
User updatedUser = userClient.updateUser(42L, user); User updatedUser = userClient.updateUser(42L, user);
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod"); assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox"); assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
} }
} }

View File

@@ -0,0 +1,115 @@
{
"provider": {
"name": "userservice"
},
"consumer": {
"name": "userclient"
},
"interactions": [
{
"description": "a request to PUT a person",
"request": {
"method": "PUT",
"path": "/user-service/users/42"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"firstName": "Zaphod",
"lastName": "Beeblebrox"
},
"matchingRules": {
"body": {
"$.firstName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.lastName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
},
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "person 42 exists"
}
]
},
{
"description": "a request to POST a person",
"request": {
"method": "POST",
"path": "/user-service/users"
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"body": {
"id": 42
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json"
}
],
"combine": "AND"
}
},
"body": {
"$.id": {
"matchers": [
{
"match": "integer"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "provider accepts a new person"
}
]
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.20"
}
}
}

View File

@@ -0,0 +1,4 @@
# Structuring a Spring Boot App into Modules and Layers
## Companion Blog Article
The companion blog article to this repository can be found [here](https://reflectoring.io/testing-verticals-and-layers-spring-boot/).

View File

@@ -5,7 +5,7 @@ Read the [companion blog article](https://reflectoring.io/consumer-driven-contra
## Getting Started ## Getting Started
* have a look at the [contract](/src/test/resources/contracts) * have a look at the [contract](src/test/resources/contracts/userservice)
* have a look at the [controller](/src/main/java/io/reflectoring/UserController.java) * have a look at the [controller](src/main/java/io/reflectoring/UserController.java)
* run `./gradlew generateContractTests` to generate JUnit tests that validate the controller against the contract * run `./gradlew generateContractTests` to generate JUnit tests that validate the controller against the contract
* run `./gradlew build` to generate and run the tests * run `./gradlew build` to generate and run the tests