Updated documentation for generating Spring REST Docs snippets (#648)

* Updated documentation with examples how to generate Spring REST Docs snippets from the generated tests.

fixes gh-613
This commit is contained in:
Tim Ysewyn
2018-05-09 07:57:13 +02:00
committed by Marcin Grzejszczak
parent 17327462ad
commit 8c5f2f5567
8 changed files with 256 additions and 6 deletions

View File

@@ -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}/../../

View File

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

View File

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

View File

@@ -21,6 +21,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud-contract.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-contract.version>
<spring.cloud.contract.verifier.skip>true</spring.cloud.contract.verifier.skip>
</properties>
<dependencies>
@@ -43,16 +44,23 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<!-- tag::dependencies[] -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<optional>true</optional>
</dependency>
<!-- end::dependencies[] -->
</dependencies>
<dependencyManagement>
@@ -113,6 +121,26 @@
</execution>
</executions>
</plugin>
<!-- tag::plugin[] -->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*standalone.*</contractPackageRegex>
<baseClassFQN>com.example.fraud.FraudBaseWithStandaloneSetup</baseClassFQN>
</baseClassMapping>
<baseClassMapping>
<contractPackageRegex>.*webapp.*</contractPackageRegex>
<baseClassFQN>com.example.fraud.FraudBaseWithWebAppSetup</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
</configuration>
</plugin>
<!-- end::plugin[] -->
</plugins>
</build>

View File

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

View File

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

View File

@@ -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.*`
*/

View File

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