diff --git a/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc b/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc
index ce6cfcdaa7..21183b4e5f 100644
--- a/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-contract-verifier.adoc
@@ -9,6 +9,7 @@
:standalone_samples_path: {samples_path}/standalone/dsl
:standalone_messaging_samples_path: {samples_path}/standalone/messaging
:standalone_pact_path: {samples_path}/standalone/pact
+:standalone_restdocs_path: {samples_path}/standalone/restdocs
:tests_path: {core_path}/tests
:samples_url: https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/master
:introduction_url: ${core_path}/../../
diff --git a/docs/src/main/asciidoc/verifier_contract.adoc b/docs/src/main/asciidoc/verifier_contract.adoc
index 335e52d1bf..08337c667b 100644
--- a/docs/src/main/asciidoc/verifier_contract.adoc
+++ b/docs/src/main/asciidoc/verifier_contract.adoc
@@ -1270,6 +1270,40 @@ case, the contract had an index of `1` in the list of contracts in the file).
TIP: As you can see, it iss much better if you name your contracts because doing so makes
your tests far more meaningful.
+=== Generating Spring REST Docs snippets from the contracts
+
+When you want to include the requests and responses of your API using Spring REST Docs,
+you only need to make some minor changes to your setup if you are using MockMvc and RestAssuredMockMvc.
+Simply include the following dependencies if you haven't already.
+
+[source,xml,indent=0]
+.Maven
+----
+include::{standalone_restdocs_path}/http-server/pom.xml[tags=dependencies,indent=0]
+----
+
+[source,groovy,indent=0]
+.Gradle
+----
+include::{standalone_restdocs_path}/http-server/build.gradle[tags=dependencies,indent=0]
+----
+
+Next you need to make some changes to your base class like the following example.
+
+[source,java,indent=0]
+----
+include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java[tags=base_class,indent=0]
+----
+
+In case you are using the standalone setup, you can set up RestAssuredMockMvc like this:
+
+[source,java,indent=0]
+----
+include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java[tags=base_class,indent=0]
+----
+
+TIP: You don't need to specify the output directory for the generated snippets since version 1.2.0.RELEASE of Spring REST Docs.
+
== Customization
IMPORTANT: This section is valid only for Groovy DSL
diff --git a/samples/standalone/restdocs/http-server/build.gradle b/samples/standalone/restdocs/http-server/build.gradle
index 4144159e31..5797f5c1f7 100644
--- a/samples/standalone/restdocs/http-server/build.gradle
+++ b/samples/standalone/restdocs/http-server/build.gradle
@@ -11,6 +11,7 @@ buildscript {
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE"
+ classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${project.findProperty('verifierVersion') ?: verifierVersion}"
}
}
@@ -30,6 +31,7 @@ apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'maven-publish'
apply plugin: 'maven'
+apply plugin: 'spring-cloud-contract'
dependencyManagement {
imports {
@@ -44,8 +46,11 @@ dependencies {
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
testCompile 'org.springframework.boot:spring-boot-starter-test'
- testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'
testCompile 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
+ // tag::dependencies[]
+ testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
+ testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'
+ // end::dependencies[]
}
test {
@@ -55,6 +60,15 @@ test {
}
}
+contracts {
+ baseClassMappings {
+ baseClassMapping('.*standalone.*', 'com.example.fraud.FraudBaseWithStandaloneSetup')
+ baseClassMapping('.*webapp.*', 'com.example.fraud.FraudBaseWithWebAppSetup')
+ }
+}
+
+generateClientStubs.enabled = false
+
task wrapper(type: Wrapper) {
gradleVersion = '4.0.2'
}
diff --git a/samples/standalone/restdocs/http-server/pom.xml b/samples/standalone/restdocs/http-server/pom.xml
index 4e08c64a67..db18d0aa9d 100644
--- a/samples/standalone/restdocs/http-server/pom.xml
+++ b/samples/standalone/restdocs/http-server/pom.xml
@@ -21,6 +21,7 @@
UTF-8
1.8
2.0.0.BUILD-SNAPSHOT
+ true
@@ -43,16 +44,23 @@
test
-
- org.springframework.restdocs
- spring-restdocs-mockmvc
- true
-
org.springframework.cloud
spring-cloud-starter-contract-stub-runner
test
+
+
+ org.springframework.cloud
+ spring-cloud-starter-contract-verifier
+ test
+
+
+ org.springframework.restdocs
+ spring-restdocs-mockmvc
+ true
+
+
@@ -113,6 +121,26 @@
+
+
+ org.springframework.cloud
+ spring-cloud-contract-maven-plugin
+ ${spring-cloud-contract.version}
+ true
+
+
+
+ .*standalone.*
+ com.example.fraud.FraudBaseWithStandaloneSetup
+
+
+ .*webapp.*
+ com.example.fraud.FraudBaseWithWebAppSetup
+
+
+
+
+
diff --git a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java
new file mode 100644
index 0000000000..4484a09e8a
--- /dev/null
+++ b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java
@@ -0,0 +1,31 @@
+// tag::base_class[]
+package com.example.fraud;
+
+import io.restassured.module.mockmvc.RestAssuredMockMvc;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.springframework.restdocs.JUnitRestDocumentation;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
+
+public abstract class FraudBaseWithStandaloneSetup {
+
+ private static final String OUTPUT = "target/generated-snippets";
+
+ @Rule
+ public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);
+
+ @Rule public TestName testName = new TestName();
+
+ @Before
+ public void setup() {
+ RestAssuredMockMvc.standaloneSetup(MockMvcBuilders.standaloneSetup(new FraudDetectionController())
+ .apply(documentationConfiguration(this.restDocumentation))
+ .alwaysDo(document(getClass().getSimpleName() + "_" + testName.getMethodName())));
+ }
+
+}
+// end::base_class[]
\ No newline at end of file
diff --git a/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java
new file mode 100644
index 0000000000..ffcf09ddad
--- /dev/null
+++ b/samples/standalone/restdocs/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java
@@ -0,0 +1,47 @@
+// tag::base_class[]
+package com.example.fraud;
+
+import io.restassured.module.mockmvc.RestAssuredMockMvc;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.restdocs.JUnitRestDocumentation;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public abstract class FraudBaseWithWebAppSetup {
+
+ private static final String OUTPUT = "target/generated-snippets";
+
+ @Rule
+ public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);
+
+ @Rule public TestName testName = new TestName();
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Before
+ public void setup() {
+ RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
+ .apply(documentationConfiguration(this.restDocumentation))
+ .alwaysDo(document(getClass().getSimpleName() + "_" + testName.getMethodName()))
+ .build());
+ }
+
+ protected void assertThatRejectionReasonIsNull(Object rejectionReason) {
+ assert rejectionReason == null;
+ }
+}
+// end::base_class[]
\ No newline at end of file
diff --git a/samples/standalone/restdocs/http-server/src/test/resources/contracts/standalone/shouldMarkClientAsFraud.groovy b/samples/standalone/restdocs/http-server/src/test/resources/contracts/standalone/shouldMarkClientAsFraud.groovy
new file mode 100644
index 0000000000..10fec1a686
--- /dev/null
+++ b/samples/standalone/restdocs/http-server/src/test/resources/contracts/standalone/shouldMarkClientAsFraud.groovy
@@ -0,0 +1,66 @@
+package contracts.standalone
+
+org.springframework.cloud.contract.spec.Contract.make {
+ request { // (1)
+ method 'PUT' // (2)
+ url '/fraudcheck' // (3)
+ body([ // (4)
+ clientId: $(c(regex('[0-9]{10}')), p("8532032713")),
+ loanAmount: 99999
+ ])
+ headers { // (5)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+ response { // (6)
+ status OK() // (7)
+ body([ // (8)
+ fraudCheckStatus: "FRAUD",
+ rejectionReason: "Amount too high"
+ ])
+ headers { // (9)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+}
+
+/*
+Since we don't want to force on the user to hardcode values of fields that are dynamic
+(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's
+ value in a `$(...)` or `value(...)` and provide a dynamic value of a field then
+ the concrete value will be generated for you. If you want to be really explicit about
+ which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation.
+ That way what's present in the `consumer` section will end up in the produced stub. What's
+ there in the `producer` will end up in the autogenerated test. If you provide only the
+ regular expression side without the concrete value then Spring Cloud Contract will generate one for you.
+
+From the Consumer perspective, when shooting a request in the integration test:
+
+(1) - If the consumer sends a request
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the response will be sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+
+From the Producer perspective, in the autogenerated producer-side test:
+
+(1) - A request will be sent to the producer
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the test will assert if the response has been sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
+ */
\ No newline at end of file
diff --git a/samples/standalone/restdocs/http-server/src/test/resources/contracts/webapp/shouldMarkClientAsNotFraud.groovy b/samples/standalone/restdocs/http-server/src/test/resources/contracts/webapp/shouldMarkClientAsNotFraud.groovy
new file mode 100644
index 0000000000..e2ad1731c8
--- /dev/null
+++ b/samples/standalone/restdocs/http-server/src/test/resources/contracts/webapp/shouldMarkClientAsNotFraud.groovy
@@ -0,0 +1,29 @@
+package contracts.webapp
+
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'PUT'
+ url '/fraudcheck'
+ body("""
+ {
+ "clientId":"${value(consumer(regex('[0-9]{10}')), producer('1234567890'))}",
+ "loanAmount":123.123
+ }
+ """
+ )
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+
+ }
+ response {
+ status OK()
+ body(
+ fraudCheckStatus: "OK",
+ rejectionReason: $(consumer(null), producer(execute('assertThatRejectionReasonIsNull($it)')))
+ )
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
+}