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:
committed by
Marcin Grzejszczak
parent
17327462ad
commit
8c5f2f5567
@@ -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}/../../
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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[]
|
||||
@@ -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[]
|
||||
@@ -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.*`
|
||||
*/
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user