Compare commits

..

32 Commits

Author SHA1 Message Date
Tom Hombergs
500ee0a04f ignored test because it cannot run on CI 2018-05-27 21:55:26 +02:00
Tom Hombergs
4d67dd44d0 fix 2018-05-27 21:10:15 +02:00
Tom Hombergs
a4d70d5835 Example application for structuring a Spring Boot app 2018-05-27 20:55:24 +02:00
Tom Hombergs
f2b83425ac inital checking for spring-boot-test example project 2018-05-20 20:38:28 +02:00
Tom Hombergs
f854262f80 changed package name 2018-03-18 22:44:54 +01:00
Tom Hombergs
11d70c0c12 pact feign consumer example 2018-03-17 12:58:11 +01:00
Tom Hombergs
4e26aa589e moved deprecated modules 2018-03-17 12:04:05 +01:00
Tom Hombergs
5d76ba1375 added example for modularizing a Spring Boot application 2018-02-01 23:32:01 +01:00
Tom Hombergs
1d50d8f513 ignoring offline cdc test 2018-01-18 22:48:20 +01:00
Tom Hombergs
0e4b84b72e polishing spring-cloud-contract consumer example 2018-01-18 22:19:42 +01:00
Tom Hombergs
60de5fafac Merge remote-tracking branch 'origin/master' 2018-01-17 21:45:54 +01:00
Tom Hombergs
21b2bccbad added spring cloud contract consumer example 2018-01-17 21:45:43 +01:00
Tom Hombergs
f4a0addd24 Update README.md 2018-01-15 16:40:22 +01:00
Tom Hombergs
a5e7696775 added pact contract 2018-01-13 12:53:21 +01:00
Tom Hombergs
d68a99e92b polishing and readme 2018-01-10 00:09:05 +01:00
Tom Hombergs
3a6149b190 added links to blog posts 2018-01-08 22:51:48 +01:00
Tom Hombergs
2c775cdaa2 refactored gradle project structure 2018-01-08 22:32:55 +01:00
Tom Hombergs
387d9a5711 Merge pull request #5 from thombergs/spring-cloud-contract
added example with spring cloud contract
2018-01-08 22:05:48 +01:00
Tom Hombergs
99f6e3fb71 added example with spring cloud contract 2018-01-08 20:23:04 +01:00
Tom Hombergs
7bce17b055 upgraded pact-web and pact-jvm-provider-spring 2018-01-02 23:01:38 +01:00
Tom Hombergs
c9ae2ca600 updated pact between Angular app and Spring provider 2017-12-31 15:25:57 +01:00
Tom Hombergs
5f46041ccd updated gradle 2017-12-31 15:25:06 +01:00
Tom Hombergs
d139eecb2d updated angular-pact example 2017-12-10 20:24:55 +01:00
Tom Hombergs
671f5e501e moved angular-pact to pact-angular 2017-12-09 13:30:06 +01:00
Tom Hombergs
a885f98f2f fixed dependency to pact-node 2017-11-13 23:17:08 +01:00
Tom Hombergs
f10c8d2bfc added angular pact example 2017-11-06 23:16:47 +01:00
Tom Hombergs
b93c05fbe5 fixed unit test 2017-10-18 21:01:54 +02:00
Tom Hombergs
8e69a627cf Merge pull request #3 from thombergs/angular-pact
Angular pact
2017-10-18 20:32:07 +02:00
Tom Hombergs
c8af61e065 Merge remote-tracking branch 'origin/master' 2017-10-10 23:14:27 +02:00
Tom Hombergs
d1c5091ee8 added junit examples 2017-10-10 23:14:19 +02:00
Tom Hombergs
83b7eb429e Merge pull request #1 from snicoll/patch-1
Remove unnecessary EnableAutoConfiguration
2017-09-19 20:23:18 +02:00
Stéphane Nicoll
3e866fbe44 Remove unnecessary EnableAutoConfiguration 2017-09-19 08:41:04 +02:00
308 changed files with 4869 additions and 752 deletions

View File

@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="Gradle: org.projectlombok:lombok:1.16.20">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/ac76d9b956045631d1561a09289cbf472e077c01/lombok-1.16.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/69ebf81bb97bdb3c9581c171762bb4929cb5289c/lombok-1.16.20-sources.jar!/" />
</SOURCES>
</library>
</component>

13
.idea/modules/spring-boot-testing.iml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="spring-boot-testing" external.linked.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.root.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.system.id="GRADLE" external.system.module.group="reflectoring.io" external.system.module.version="0.0.1-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing">
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/build" />
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/out" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -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

View File

@@ -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

View File

@@ -3,4 +3,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-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip

View File

@@ -0,0 +1,3 @@
# Examples with JUnit 4 and JUnit 5
Have a look at [the code](/src/test/java/com/example/demo/)

View File

@@ -0,0 +1,25 @@
buildscript {
repositories {
mavenCentral()
}
}
apply plugin: 'java'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
ext {
springCloudVersion = 'Dalston.SR2'
}
dependencies {
testCompile 'org.junit.jupiter:junit-jupiter-engine:5.0.1'
testCompile 'junit:junit:4.12'
}

View File

@@ -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

View File

@@ -0,0 +1,15 @@
package com.example.demo.connectionchecking;
public class ConnectionChecker {
private String uri;
public ConnectionChecker(String uri) {
this.uri = uri;
}
public boolean connect() {
return false;
}
}

View File

@@ -0,0 +1,31 @@
package com.example.demo.connectionchecking.junit4;
import com.example.demo.connectionchecking.ConnectionChecker;
import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class AssumingConnection implements TestRule {
private ConnectionChecker checker;
public AssumingConnection(ConnectionChecker checker) {
this.checker = checker;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
if (!checker.connect()) {
throw new AssumptionViolatedException("Could not connect. Skipping test!");
} else {
base.evaluate();
}
}
};
}
}

View File

@@ -0,0 +1,19 @@
package com.example.demo.connectionchecking.junit4;
import com.example.demo.connectionchecking.ConnectionChecker;
import org.junit.ClassRule;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ConnectionCheckingJunit4Test {
@ClassRule
public static AssumingConnection assumingConnection = new AssumingConnection(new ConnectionChecker("http://my.integration.system"));
@Test
public void testOnlyWhenConnected() {
fail("Booh!");
}
}

View File

@@ -0,0 +1,14 @@
package com.example.demo.connectionchecking.junit5;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.extension.ExtendWith;
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(AssumeConnectionCondition.class)
public @interface AssumeConnection {
String uri();
}

View File

@@ -0,0 +1,28 @@
package com.example.demo.connectionchecking.junit5;
import java.util.Optional;
import com.example.demo.connectionchecking.ConnectionChecker;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
public class AssumeConnectionCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<AssumeConnection> annotation = findAnnotation(context.getElement(), AssumeConnection.class);
if (annotation.isPresent()) {
String uri = annotation.get().uri();
ConnectionChecker checker = new ConnectionChecker(uri);
if (!checker.connect()) {
return ConditionEvaluationResult.disabled(String.format("Could not connect to '%s'. Skipping test!", uri));
} else {
return ConditionEvaluationResult.enabled(String.format("Successfully connected to '%s'. Continuing test!", uri));
}
}
return ConditionEvaluationResult.enabled("No AssumeConnection annotation found. Continuing test.");
}
}

View File

@@ -0,0 +1,14 @@
package com.example.demo.connectionchecking.junit5;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
@AssumeConnection(uri = "http://my.integration.system")
public class ConnectionCheckingJunit5Test {
@Test
public void testOnlyWhenConnected() {
fail("Booh!");
}
}

View File

@@ -0,0 +1,120 @@
{
"provider": {
"name": "customerServiceProvider"
},
"consumer": {
"name": "addressClient"
},
"interactions": [
{
"description": "a request to the address collection resource",
"request": {
"method": "GET",
"path": "/addresses/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/hal+json"
},
"body": {
"_embedded": {
"addresses": [
{
"street": "Elm Street",
"_links": {
"self": {
"href": "http://localhost:8080/addresses/1"
},
"address": {
"href": "http://localhost:8080/addresses/1"
},
"customer": {
"href": "http://localhost:8080/addresses/1/customer"
}
}
},
{
"street": "High Street",
"_links": {
"self": {
"href": "http://localhost:8080/addresses/2"
},
"address": {
"href": "http://localhost:8080/addresses/2"
},
"customer": {
"href": "http://localhost:8080/addresses/2/customer"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/addresses{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://localhost:8080/profile/addresses"
},
"search": {
"href": "http://localhost:8080/addresses/search"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
},
"providerStates": [
{
"name": "a collection of 2 addresses"
}
]
},
{
"description": "a request to the address resource",
"request": {
"method": "GET",
"path": "/addresses/1"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/hal+json"
},
"body": {
"street": "Elm Street",
"_links": {
"self": {
"href": "http://localhost:8080/addresses/1"
},
"address": {
"href": "http://localhost:8080/addresses/1"
},
"customer": {
"href": "http://localhost:8080/addresses/1/customer"
}
}
}
},
"providerStates": [
{
"name": "a single address"
}
]
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.2"
}
}
}

View File

@@ -18,3 +18,13 @@ a consumer against the Pact.
Run `npm install` to load the needed javascript libraries and then `npm run test` to
run the tests. After the tests have successfully run the created pact file will be
created in the folder `pacts`.
Then, you can call `npm run publish-pacts` to publish the pact files to a [Pact Broker](https://github.com/pact-foundation/pact_broker).
You must set the following npm configs for the `publish-pacts` script to work:
```
npm config set angular-pact:brokerUrl <URL>
npm config set angular-pact:brokerUsername <USER>
npm config set angular-pact:brokerPassword <PASS>
```

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,8 @@
"build": "ng build",
"test": "cross-env LOGLEVEL=DEBUG ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"e2e": "ng e2e",
"publish-pacts": "node publish-pacts.js"
},
"private": true,
"dependencies": {
@@ -45,9 +46,9 @@
"ts-node": "~3.2.0",
"tslint": "~5.7.0",
"typescript": "~2.3.3",
"@pact-foundation/pact-node": "~4.12",
"@pact-foundation/karma-pact": "~2.1.0",
"pact-web": "~3.0",
"@pact-foundation/pact-node": "6.5.0",
"@pact-foundation/karma-pact": "2.1.3",
"@pact-foundation/pact-web": "5.3.0",
"cross-env": "^5.0.5"
}
}

View File

@@ -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"
}
}
}

View File

@@ -0,0 +1,20 @@
let projectFolder = __dirname;
let pact = require('@pact-foundation/pact-node');
let project = require('./package.json');
let pactBrokerUrl = process.env.npm_package_config_brokerUrl;
let pactBrokerUsername = process.env.npm_package_config_brokerUsername;
let pactBrokerPassword = process.env.npm_package_config_brokerPassword;
let options = {
pactFilesOrDirs: [projectFolder + '/pacts'],
pactBroker: pactBrokerUrl,
consumerVersion: project.version,
tags: ['latest'],
pactBrokerUsername: pactBrokerUsername,
pactBrokerPassword: pactBrokerPassword
};
pact.publishPacts(options).then(function () {
console.log("Pacts successfully published!");
});

View File

@@ -2,20 +2,18 @@ import {TestBed} from '@angular/core/testing';
import {HttpClientModule} from '@angular/common/http';
import {UserService} from './user.service';
import {User} from './user';
import * as Pact from 'pact-web';
import {PactWeb, Matchers} from '@pact-foundation/pact-web';
describe('UserService', () => {
let provider;
beforeAll(function (done) {
provider = Pact({
provider = new PactWeb({
consumer: 'ui',
provider: 'userservice',
web: true,
port: 1234,
host: '127.0.0.1',
logLevel: 'DEBUG'
});
// required for slower CI environments
@@ -72,7 +70,7 @@ describe('UserService', () => {
},
willRespondWith: {
status: 201,
body: Pact.Matchers.somethingLike({
body: Matchers.somethingLike({
id: createdUserId
}),
headers: {
@@ -94,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: Matchers.somethingLike(expectedUser),
headers: {
'Content-Type': 'application/json'
}
},
willRespondWith: {
status: 200,
body: 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);
});
});
});
});

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,13 @@
# Testing a Spring Boot REST API Consumer against a Contract with Spring Cloud Contract
## Companion Blog Article
Read the [companion blog article](https://reflectoring.io/consumer-driven-contract-consumer-spring-cloud-contract/) to this repository.
## 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 [consumer test](/src/test/java/io/reflectoring/UserClientTest.java)
* run `./gradlew publishToMavenLocal` in the [producer project](../spring-cloud-contract-provider)
to create a provider stubs
* run `./gradlew build` in this project to verify the feign client against the stub

View File

@@ -0,0 +1,5 @@
userservice:
ribbon:
eureka:
enabled: false
listOfServers: localhost:8080

View File

@@ -0,0 +1,27 @@
apply plugin: 'org.springframework.boot'
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}"
}
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}")
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}")
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE")
compile('com.h2database:h2:1.4.196')
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
testCompile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.2")
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}")
}

View File

@@ -0,0 +1,2 @@
springboot_version=1.5.9.RELEASE
verifier_version=1.2.2.RELEASE

Some files were not shown because too many files have changed in this diff Show More