Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test.
Introduction
The aim of Spring REST Docs is to help you produce accurate and readable documentation for your RESTful services.
Writing high-quality documentation is difficult. One way to ease that difficulty is to use tools that are well-suited to the job. To this end, Spring REST Docs uses Asciidoctor by default. Asciidoctor processes plain text and produces HTML, styled and laid out to suit your needs. If you prefer, you can also configure Spring REST Docs to use Markdown.
Spring REST Docs uses snippets produced by tests written with Spring MVC’s
test framework, Spring
WebFlux’s WebTestClient
or
REST Assured 3. This test-driven approach helps to guarantee
the accuracy of your service’s documentation. If a snippet is incorrect, the test that
produces it fails.
Documenting a RESTful service is largely about describing its resources. Two key parts of each resource’s description are the details of the HTTP requests that it consumes and the HTTP responses that it produces. Spring REST Docs lets you work with these resources and the HTTP requests and responses, shielding your documentation from the inner-details of your service’s implementation. This separation helps you document your service’s API rather than its implementation. It also frees you to evolve the implementation without having to rework the documentation.
Getting started
This section describes how to get started with Spring REST Docs.
Sample Applications
If you want to jump straight in, a number of sample applications are available:
Sample | Build system | Description |
---|---|---|
Maven |
Demonstrates the creation of a getting started guide and an API guide for a service implemented by using Spring Data REST. |
|
Gradle |
Demonstrates the creation of a getting started guide and an API guide for a service implemented by using Spring HATEOAS. |
Sample | Build system | Description |
---|---|---|
Gradle |
Demonstrates the use of Spring REST docs with Spring WebFlux’s WebTestClient. |
Sample | Build system | Description |
---|---|---|
Gradle |
Demonstrates the use of Spring REST Docs with REST Assured. |
Sample | Build system | Description |
---|---|---|
Gradle |
Demonstrates the use of Spring REST Docs with Markdown and Slate. |
|
Gradle |
Demonstrates the use of Spring REST Docs with TestNG. |
|
Gradle |
Demonstrates the use of Spring REST Docs with JUnit 5. |
Requirements
Spring REST Docs has the following minimum requirements:
-
Java 8
-
Spring Framework 5 (5.0.2 or later)
Additionally, the spring-restdocs-restassured
module requires REST Assured 3.0.
Build configuration
The first step in using Spring REST Docs is to configure your project’s build. The
Spring HATEOAS and
Spring Data REST samples contain a build.gradle
and pom.xml
, respectively, that you may wish to use as a reference. The key parts of
the configuration are described in the following listings:
<dependency> (1)
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<version>{project-version}</version>
<scope>test</scope>
</dependency>
<build>
<plugins>
<plugin> (2)
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase> (3)
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency> (4)
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>{project-version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
1 | Add a dependency on spring-restdocs-mockmvc in the test scope. If you want to use
WebTestClient or REST Assured rather than MockMvc, add a dependency on
spring-restdocs-webtestclient or spring-restdocs-restassured respectively instead. |
2 | Add the Asciidoctor plugin. |
3 | Using prepare-package allows the documentation to be
included in the package. |
4 | Add spring-restdocs-asciidoctor as a dependency of the Asciidoctor plugin. This
will automatically configure the snippets attribute for use in your .adoc files to
point to target/generated-snippets . It will also allow you to use the operation
block macro. |
plugins { (1)
id "org.asciidoctor.convert" version "1.5.9.2"
}
dependencies {
asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' (2)
testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' (3)
}
ext { (4)
snippetsDir = file('build/generated-snippets')
}
test { (5)
outputs.dir snippetsDir
}
asciidoctor { (6)
inputs.dir snippetsDir (7)
dependsOn test (8)
}
1 | Apply the Asciidoctor plugin. |
2 | Add a dependency on spring-restdocs-asciidoctor in the asciidoctor configuration.
This will automatically configure the snippets attribute for use in your .adoc
files to point to build/generated-snippets . It will also allow you to use the
operation block macro. |
3 | Add a dependency on spring-restdocs-mockmvc in the testCompile configuration. If
you want to use WebTestClient or REST Assured rather than MockMvc, add a dependency
on spring-restdocs-webtestclient or spring-restdocs-restassured respectively
instead. |
4 | Configure a property to define the output location for generated snippets. |
5 | Configure the test task to add the snippets directory as an output. |
6 | Configure the asciidoctor task |
7 | Configure the snippets directory as an input. |
8 | Make the task depend on the test task so that the tests are run before the documentation is created. |
Packaging the Documentation
You may want to package the generated documentation in your project’s jar file — for example, to have it served as static content by Spring Boot. To do so, configure your project’s build so that:
-
The documentation is generated before the jar is built
-
The generated documentation is included in the jar
The following listings show how to do so in both Maven and Gradle:
<plugin> (1)
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<!-- … -->
</plugin>
<plugin> (2)
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration> (3)
<outputDirectory>
${project.build.outputDirectory}/static/docs
</outputDirectory>
<resources>
<resource>
<directory>
${project.build.directory}/generated-docs
</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
1 | The existing declaration for the Asciidoctor plugin. |
2 | The resource plugin must be declared after the Asciidoctor plugin as they are bound
to the same phase (prepare-package ) and the resource plugin must run after the
Asciidoctor plugin to ensure that the documentation is generated before it’s copied. |
3 | Copy the generated documentation into the build output’s static/docs directory,
from where it will be included in the jar file. |
bootJar {
dependsOn asciidoctor (1)
from ("${asciidoctor.outputDir}/html5") { (2)
into 'static/docs'
}
}
1 | Ensure that the documentation has been generated before the jar is built. |
2 | Copy the generated documentation into the jar’s static/docs directory. |
Generating Documentation Snippets
Spring REST Docs uses Spring MVC’s
test framework,
Spring WebFlux’s WebTestClient
, or
REST Assured to make requests to the service that you are
documenting. It then produces documentation snippets for the request and the resulting
response.
Setting up Your Tests
Exactly how you set up your tests depends on the test framework that you use. Spring REST Docs provides first-class support for JUnit 4 and JUnit 5. Other frameworks, such as TestNG, are also supported, although slightly more setup is required.
Setting up Your JUnit 4 Tests
When using JUnit 4, the first step in generating documentation snippets is to declare a
public
JUnitRestDocumentation
field that is annotated as a JUnit @Rule
.
The following example shows how to do so:
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
By default, the JUnitRestDocumentation
rule is automatically configured with an output
directory based on your project’s build tool:
Build tool | Output directory |
---|---|
Maven |
|
Gradle |
|
You can override the default by providing an output directory when you create the
JUnitRestDocumentation
instance.
The following example shows how to do so:
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("custom");
Next, you must provide an @Before
method to configure MockMvc, WebTestClient or REST
Assured. The following examples show how to do so:
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)) (1)
.build();
}
1 | The MockMvc instance is configured by using a MockMvcRestDocumentationConfigurer .
You can obtain an instance of this class from the static documentationConfiguration()
method on org.springframework.restdocs.mockmvc.MockMvcRestDocumentation . |
private WebTestClient webTestClient;
@Autowired
private ApplicationContext context;
@Before
public void setUp() {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.filter(documentationConfiguration(this.restDocumentation)) (1)
.build();
}
1 | The WebTestClient instance is configured by adding a
WebTestclientRestDocumentationConfigurer as an ExchangeFilterFunction . You can obtain
an instance of this class from the static documentationConfiguration() method on
org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation . |
private RequestSpecification spec;
@Before
public void setUp() {
this.spec = new RequestSpecBuilder().addFilter(
documentationConfiguration(this.restDocumentation)) (1)
.build();
}
1 | REST Assured is configured by adding a RestAssuredRestDocumentationConfigurer as a
Filter . You can obtain an instance of this class from the static
documentationConfiguration() method on RestAssuredRestDocumentation in the
org.springframework.restdocs.restassured3 package. |
The configurer applies sensible defaults and also provides an API for customizing the configuration. See the configuration section for more information.
Setting up Your JUnit 5 Tests
When using JUnit 5, the first step in generating documentation snippets is to apply
the RestDocumentationExtension
to your test class.
The following example shows how to do so:
@ExtendWith(RestDocumentationExtension.class)
public class JUnit5ExampleTests {
When testing a typical Spring application, you should also apply the SpringExtension
:
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
public class JUnit5ExampleTests {
The RestDocumentationExtension
is automatically configured with an output directory
based on your project’s build tool:
Build tool | Output directory |
---|---|
Maven |
|
Gradle |
|
If you are using JUnit 5.1, you can override the default by registering the extension as a field in your test class and providing an output directory when creating it. The following example shows how to do so:
public class JUnit5ExampleTests {
@RegisterExtension
final RestDocumentationExtension restDocumentation = new RestDocumentationExtension ("custom");
}
Next, you must provide a @BeforeEach
method to configure MockMvc, WebTestClient, or
REST Assured. The following listings show how to do so:
private MockMvc mockMvc;
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation)) (1)
.build();
}
1 | The MockMvc instance is configured by using a MockMvcRestDocumentationConfigurer .
You can obtain an instance of this class from the static documentationConfiguration()
method on org.springframework.restdocs.mockmvc.MockMvcRestDocumentation . |
private WebTestClient webTestClient;
@BeforeEach
public void setUp(ApplicationContext applicationContext,
RestDocumentationContextProvider restDocumentation) {
this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext)
.configureClient()
.filter(documentationConfiguration(restDocumentation)) (1)
.build();
}
1 | The WebTestClient instance is configured by adding a
WebTestClientRestDocumentationConfigurer as an ExchangeFilterFunction . You can obtain
an instance of this class from the static documentationConfiguration() method on
org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation . |
private RequestSpecification spec;
@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation)) (1)
.build();
}
1 | REST Assured is configured by adding a RestAssuredRestDocumentationConfigurer as a
Filter . You can obtain an instance of this class from the static
documentationConfiguration() method on RestAssuredRestDocumentation in the
org.springframework.restdocs.restassured3 package. |
The configurer applies sensible defaults and also provides an API for customizing the configuration. See the configuration section for more information.
Setting up your tests without JUnit
The configuration when JUnit is not being used is largely similar to when it is being used. This section describes the key differences. The TestNG sample also illustrates the approach.
The first difference is that you should use ManualRestDocumentation
in place of
JUnitRestDocumentation
. Also, you do not need the @Rule
annotation.
the following example shows hos to use ManualRestDocumentation
:
private ManualRestDocumentation restDocumentation = new ManualRestDocumentation();
Secondly, you must call ManualRestDocumentation.beforeTest(Class, String)
before each test. You can do so as part of the method that
configures MockMvc, WebTestClient, or REST Assured.
The following examples show how to do so:
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@BeforeMethod
public void setUp(Method method) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}
private WebTestClient webTestClient;
@Autowired
private ApplicationContext context;
@BeforeMethod
public void setUp(Method method) {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.filter(documentationConfiguration(this.restDocumentation)) (1)
.build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}
private RequestSpecification spec;
@BeforeMethod
public void setUp(Method method) {
this.spec = new RequestSpecBuilder().addFilter(
documentationConfiguration(this.restDocumentation))
.build();
this.restDocumentation.beforeTest(getClass(), method.getName());
}
Finally, you must call ManualRestDocumentation.afterTest
after each test.
The following example shows how to do so with TestNG:
@AfterMethod
public void tearDown() {
this.restDocumentation.afterTest();
}
Invoking the RESTful Service
Now that you have configured the testing framework, you can use it to invoke the RESTful service and document the request and response. The following examples show how to do so:
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) (1)
.andExpect(status().isOk()) (2)
.andDo(document("index")); (3)
1 | Invoke the root (/ ) of the service and indicate that an application/json response
is required. |
2 | Assert that the service produced the expected response. |
3 | Document the call to the service, writing the snippets into a directory named index
(which is located beneath the configured output directory). The snippets are written by
a RestDocumentationResultHandler . You can obtain an instance of this class from the
static document method on
org.springframework.restdocs.mockmvc.MockMvcRestDocumentation . |
this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON) (1)
.exchange().expectStatus().isOk() (2)
.expectBody().consumeWith(document("index")); (3)
1 | Invoke the root (/ ) of the service and indicate that an application/json response
is required. |
2 | Assert that the service produced the expected response. |
3 | Document the call to the service, writing the snippets into a directory named index
(which is located beneath the configured output directory). The snippets are written by
a Consumer of the ExchangeResult . You can obtain such a consumer from the static
document method on
org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation . |
RestAssured.given(this.spec) (1)
.accept("application/json") (2)
.filter(document("index")) (3)
.when().get("/") (4)
.then().assertThat().statusCode(is(200)); (5)
1 | Apply the specification that was initialized in the @Before method. |
2 | Indicate that an application/json response is required. |
3 | Document the call to the service, writing the snippets into a directory named index
(which is located beneath the configured output directory). The snippets are written by
a RestDocumentationFilter . You can obtain an instance of this class from the static
document method on RestAssuredRestDocumentation in the
org.springframework.restdocs.restassured3 package. |
4 | Invoke the root (/ ) of the service. |
5 | Assert that the service produce the expected response. |
By default, six snippets are written:
-
<output-directory>/index/curl-request.adoc
-
<output-directory>/index/http-request.adoc
-
<output-directory>/index/http-response.adoc
-
<output-directory>/index/httpie-request.adoc
-
<output-directory>/index/request-body.adoc
-
<output-directory>/index/response-body.adoc
See Documenting your API for more information about these and other snippets that can be produced by Spring REST Docs.
Using the Snippets
Before using the generated snippets, you must create an .adoc
source file. You can name
the file whatever you like as long as it has a .adoc
suffix. The resulting HTML file
has the same name but with an .html
suffix. The default location of the source files and
the resulting HTML files depends on whether you use Maven or Gradle:
Build tool | Source files | Generated files |
---|---|---|
Maven |
|
|
Gradle |
|
|
You can then include the generated snippets in the manually created Asciidoc file
(described earlier in this section) by using the
include macro.
You can use the snippets
attribute that is automatically set by
spring-restdocs-asciidoctor
configured in the
build configuration to reference the snippets
output directory. The following example shows how to do so:
include::{snippets}/index/curl-request.adoc[]
Documenting your API
This section provides more details about using Spring REST Docs to document your API.
Hypermedia
Spring REST Docs provides support for documenting the links in a hypermedia-based API. The following examples show how to use it:
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", links( (1)
linkWithRel("alpha").description("Link to the alpha resource"), (2)
linkWithRel("bravo").description("Link to the bravo resource")))); (3)
1 | Configure Spring REST docs to produce a snippet describing the response’s links.
Uses the static links method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | Expect a link whose rel is alpha . Uses the static linkWithRel method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | Expect a link whose rel is bravo . |
this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isOk().expectBody()
.consumeWith(document("index",links( (1)
linkWithRel("alpha").description("Link to the alpha resource"), (2)
linkWithRel("bravo").description("Link to the bravo resource")))); (3)
1 | Configure Spring REST docs to produce a snippet describing the response’s links.
Uses the static links method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | Expect a link whose rel is alpha . Uses the static linkWithRel method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | Expect a link whose rel is bravo . |
RestAssured.given(this.spec)
.accept("application/json")
.filter(document("index", links( (1)
linkWithRel("alpha").description("Link to the alpha resource"), (2)
linkWithRel("bravo").description("Link to the bravo resource")))) (3)
.get("/").then().assertThat().statusCode(is(200));
1 | Configure Spring REST docs to produce a snippet describing the response’s links.
Uses the static links method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | Expect a link whose rel is alpha . Uses the static linkWithRel method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | Expect a link whose rel is bravo . |
The result is a snippet named links.adoc
that contains a table describing the resource’s
links.
If a link in the response has a title , you can omit the description from its
descriptor and the title is used. If you omit the description and the link does
not have a title , a failure occurs.
|
When documenting links, the test fails if an undocumented link is found in the response. Similarly, the test also fails if a documented link is not found in the response and the link has not been marked as optional.
If you do not want to document a link, you can mark it as ignored. Doing so prevents it from appearing in the generated snippet while avoiding the failure described above.
You can also document links in a relaxed mode, where any undocumented links do not cause
a test failure. To do so, use the relaxedLinks
method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation
. This can be useful when
documenting a particular scenario where you only want to focus on a subset of the links.
Hypermedia Link Formats
Two link formats are understood by default:
-
Atom: Links are expected to be in an array named
links
. This is used by default when the content type of the response is compatible withapplication/json
. -
HAL: Links are expected to be in a map named
_links
. This is used by default when the content type of the response is compatible withapplication/hal+json
.
If you use Atom- or HAL-format links but with a different content type, you can provide
one of the built-in LinkExtractor
implementations to links
. The following examples
show how to do so:
.andDo(document("index", links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | Indicate that the links are in HAL format. Uses the static halLinks method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
.consumeWith(document("index",links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | Indicate that the links are in HAL format. Uses the static halLinks method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
.filter(document("index", links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))))
1 | Indicate that the links are in HAL format. Uses the static halLinks method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
If your API represents its links in a format other than Atom or HAL, you can provide your
own implementation of the LinkExtractor
interface to extract the links from the
response.
Ignoring Common Links
Rather than documenting links that are common to every response, such as self
and
curies
when using HAL, you may want to document them once in an overview section and
then ignore them in the rest of your API’s documentation. To do so, you can build on the
support for reusing snippets to add link
descriptors to a snippet that is preconfigured to ignore certain links. The following
example shows how to do so:
public static LinksSnippet links(LinkDescriptor... descriptors) {
return HypermediaDocumentation.links(linkWithRel("self").ignored().optional(),
linkWithRel("curies").ignored()).and(descriptors);
}
Request and Response Payloads
In addition to the hypermedia-specific support described earlier, support for general documentation of request and response payloads is also provided.
By default, Spring REST Docs automatically generates snippets for the body of the request
and the body of the response. These snippets are named request-body.adoc
and
response-body.adoc
respectively.
Request and Response Fields
To provide more detailed documentation of a request or response payload, support for documenting the payload’s fields is provided.
Consider the following payload:
{
"contact": {
"name": "Jane Doe",
"email": "[email protected]"
}
}
You can document the previous example’s fields as follows:
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index",
responseFields( (1)
fieldWithPath("contact.email")
.description("The user's email address"), (2)
fieldWithPath("contact.name").description("The user's name")))); (3)
1 | Configure Spring REST docs to produce a snippet describing the fields in the response
payload. To document a request, you can use requestFields . Both are static methods
on org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Expect a field with the path contact.email . Uses the static fieldWithPath method
on org.springframework.restdocs.payload.PayloadDocumentation . |
3 | Expect a field with the path contact.name . |
this.webTestClient.get().uri("user/5").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("user",
responseFields( (1)
fieldWithPath("contact.email").description("The user's email address"), (2)
fieldWithPath("contact.name").description("The user's name")))); (3)
1 | Configure Spring REST docs to produce a snippet describing the fields in the response
payload. To document a request, you can use requestFields . Both are static methods
on org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Expect a field with the path contact.email . Uses the static fieldWithPath method
on org.springframework.restdocs.payload.PayloadDocumentation . |
3 | Expect a field with the path contact.name . |
RestAssured.given(this.spec).accept("application/json")
.filter(document("user", responseFields( (1)
fieldWithPath("contact.name").description("The user's name"), (2)
fieldWithPath("contact.email").description("The user's email address")))) (3)
.when().get("/user/5")
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST docs to produce a snippet describing the fields in the response
payload. To document a request, you can use requestFields . Both are static methods
on org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Expect a field with the path contact.email . Uses the static fieldWithPath method
on org.springframework.restdocs.payload.PayloadDocumentation . |
3 | Expect a field with the path contact.name . |
The result is a snippet that contains a table describing the fields. For requests, this
snippet is named request-fields.adoc
. For responses, this snippet is named
response-fields.adoc
.
When documenting fields, the test fails if an undocumented field is found in the payload. Similarly, the test also fails if a documented field is not found in the payload and the field has not been marked as optional.
If you do not want to provide detailed documentation for all of the fields, an entire subsection of a payload can be documented. The following examples show how to do so:
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index",
responseFields( (1)
subsectionWithPath("contact")
.description("The user's contact details")))); (1)
1 | Document the subsection with the path contact . contact.email and contact.name
are now seen as having also been documented. Uses the static subsectionWithPath
method on org.springframework.restdocs.payload.PayloadDocumentation . |
this.webTestClient.get().uri("user/5").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("user",
responseFields(
subsectionWithPath("contact").description("The user's contact details")))); (1)
1 | Document the subsection with the path contact . contact.email and contact.name
are now seen as having also been documented. Uses the static subsectionWithPath
method on org.springframework.restdocs.payload.PayloadDocumentation . |
RestAssured.given(this.spec).accept("application/json")
.filter(document("user", responseFields(
subsectionWithPath("contact").description("The user's contact details")))) (1)
.when().get("/user/5")
.then().assertThat().statusCode(is(200));
1 | Document the subsection with the path contact . contact.email and contact.name
are now seen as having also been documented. Uses the static subsectionWithPath
method on org.springframework.restdocs.payload.PayloadDocumentation . |
subsectionWithPath
can be useful for providing a high-level overview of a particular
section of a payload. You can then produce separate, more detailed documentation for a
subsection. See Documenting a Subsection of a Request or Response Payload.
If you do not want to document a field or subsection at all, you can mark it as ignored. This prevents it from appearing in the generated snippet while avoiding the failure described earlier.
You can also document fields in a relaxed mode, where any undocumented fields do not
cause a test failure. To do so, use the relaxedRequestFields
and relaxedResponseFields
methods on
org.springframework.restdocs.payload.PayloadDocumentation
. This can be useful when
documenting a particular scenario where you want to focus only on a subset of the payload.
By default, Spring REST Docs assumes that the payload you are documenting is JSON.
If you want to document an XML payload, the content type of the request or response must
be compatible with application/xml .
|
Fields in JSON Payloads
This section describes how to work with fields in JSON payloads.
JSON Field Paths
JSON field paths use either dot notation or bracket notation. Dot notation uses '.' to
separate each key in the path (for example, a.b
). Bracket notation wraps each key in
square brackets and single quotation marks (for example, ['a']['b']
). In either case,
[]
is used to identify an array. Dot notation is more concise, but using bracket
notation enables the use of .
within a key name (for example, ['a.b']
). The two
different notations can be used in the same path (for example, a['b']
).
Consider the following JSON payload:
{
"a":{
"b":[
{
"c":"one"
},
{
"c":"two"
},
{
"d":"three"
}
],
"e.dot" : "four"
}
}
In the preceding JSON payload, the following paths are all present:
Path | Value |
---|---|
|
An object containing |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing the strings |
|
The string |
|
The string |
|
The string |
You can also document a payload that uses an array at its root. The path []
refers
to the entire array. You can then use bracket or dot notation to identify fields within
the array’s entries. For example, [].id
corresponds to the id
field of every object
found in the following array:
[
{
"id":1
},
{
"id":2
}
]
You can use *
as a wildcard to match fields with different names. For example,
users.*.role
could be used to document the role of every user in the following JSON:
{
"users":{
"ab12cd34":{
"role": "Administrator"
},
"12ab34cd":{
"role": "Guest"
}
}
}
JSON Field Types
When a field is documented, Spring REST Docs tries to determine its type by examining the payload. Seven different types are supported:
Type | Description |
---|---|
|
The value of each occurrence of the field is an array. |
|
The value of each occurrence of the field is a boolean ( |
|
The value of each occurrence of the field is an object. |
|
The value of each occurrence of the field is a number. |
|
The value of each occurrence of the field is |
|
The value of each occurrence of the field is a string. |
|
The field occurs multiple times in the payload with a variety of different types. |
You can also explicitly set the type by using the type(Object)
method on
FieldDescriptor
. The result of the toString
method of the supplied Object
is used
in the documentation. Typically, one of the values enumerated by JsonFieldType
is used.
The following examples show how to do so:
.andDo(document("index",
responseFields(
fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
.description("The user's email address"))));
1 | Set the field’s type to String . |
.consumeWith(document("user",
responseFields(
fieldWithPath("contact.email")
.type(JsonFieldType.STRING) (1)
.description("The user's email address"))));
1 | Set the field’s type to String . |
.filter(document("user", responseFields(
fieldWithPath("contact.email")
.type(JsonFieldType.STRING) (1)
.description("The user's email address"))))
1 | Set the field’s type to String . |
XML payloads
This section describes how to work with XML payloads.
XML Field Paths
XML field paths are described using XPath. /
is used to descend into a child node.
XML Field Types
When documenting an XML payload, you must provide a type for the field by using the
type(Object)
method on FieldDescriptor
. The result of the supplied type’s toString
method is used in the documentation.
Reusing Field Descriptors
In addition to the general support for reusing snippets, the request and response snippets let additional descriptors be configured with a path prefix. This lets the descriptors for a repeated portion of a request or response payload be created once and then reused.
Consider an endpoint that returns a book:
{
"title": "Pride and Prejudice",
"author": "Jane Austen"
}
The paths for title
and author
are title
and author
, respectively.
Now consider an endpoint that returns an array of books:
[{
"title": "Pride and Prejudice",
"author": "Jane Austen"
},
{
"title": "To Kill a Mockingbird",
"author": "Harper Lee"
}]
The paths for title
and author
are [].title
and [].author
, respectively. The only
difference between the single book and the array of books is that the fields' paths now
have a [].
prefix.
You can create the descriptors that document a book as follows:
FieldDescriptor[] book = new FieldDescriptor[] {
fieldWithPath("title").description("Title of the book"),
fieldWithPath("author").description("Author of the book") };
You can then use them to document a single book, as follows:
this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document("book", responseFields(book))); (1)
1 | Document title and author by using existing descriptors |
this.webTestClient.get().uri("/books/1").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("book",
responseFields(book))); (1)
1 | Document title and author by using existing descriptors |
RestAssured.given(this.spec).accept("application/json")
.filter(document("book", responseFields(book))) (1)
.when().get("/books/1")
.then().assertThat().statusCode(is(200));
1 | Document title and author by using existing descriptors |
You can also use the descriptors to document an array of books, as follows:
this.mockMvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book",
responseFields(
fieldWithPath("[]").description("An array of books")) (1)
.andWithPrefix("[].", book))); (2)
1 | Document the array. |
2 | Document [].title and [].author by using the existing descriptors prefixed with
[]. |
this.webTestClient.get().uri("/books").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("books",
responseFields(
fieldWithPath("[]")
.description("An array of books")) (1)
.andWithPrefix("[].", book))); (2)
1 | Document the array. |
2 | Document [].title and [].author by using the existing descriptors prefixed with
[]. |
RestAssured.given(this.spec).accept("application/json")
.filter(document("books", responseFields(
fieldWithPath("[]").description("An array of books")) (1)
.andWithPrefix("[].", book))) (2)
.when().get("/books")
.then().assertThat().statusCode(is(200));
1 | Document the array. |
2 | Document [].title and [].author by using the existing descriptors prefixed with
[]. |
Documenting a Subsection of a Request or Response Payload
If a payload is large or structurally complex, it can be useful to document individual sections of the payload. REST Docs let you do so by extracting a subsection of the payload and then documenting it.
Documenting a Subsection of a Request or Response Body
Consider the following JSON response body:
{
"weather": {
"wind": {
"speed": 15.3,
"direction": 287.0
},
"temperature": {
"high": 21.2,
"low": 14.8
}
}
}
You can produce a snippet that documents the temperature
object as follows:
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document("location",
responseBody(beneathPath("weather.temperature")))); (1)
1 | Produce a snippet containing a subsection of the response body. Uses the static
responseBody and beneathPath methods on
org.springframework.restdocs.payload.PayloadDocumentation . To produce a snippet for
the request body, you can use requestBody in place of responseBody . |
this.webTestClient.get().uri("/locations/1").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("temperature",
responseBody(beneathPath("weather.temperature")))); (1)
1 | Produce a snippet containing a subsection of the response body. Uses the static
responseBody and beneathPath methods on
org.springframework.restdocs.payload.PayloadDocumentation . To produce a snippet for
the request body, you can use requestBody in place of responseBody . |
RestAssured.given(this.spec).accept("application/json")
.filter(document("location", responseBody(beneathPath("weather.temperature")))) (1)
.when().get("/locations/1")
.then().assertThat().statusCode(is(200));
1 | Produce a snippet containing a subsection of the response body. Uses the static
responseBody and beneathPath methods on
org.springframework.restdocs.payload.PayloadDocumentation . To produce a snippet for
the request body, you can use requestBody in place of responseBody . |
The result is a snippet with the following contents:
{
"temperature": {
"high": 21.2,
"low": 14.8
}
}
To make the snippet’s name distinct, an identifier for the subsection is included. By
default, this identifier is beneath-${path}
. For example, the preceding code results in
a snippet named response-body-beneath-weather.temperature.adoc
. You can customized the
identifier by using the withSubsectionId(String)
method, as follows:
responseBody(beneathPath("weather.temperature").withSubsectionId("temp"));
The result is a snippet named request-body-temp.adoc
.
Documenting the Fields of a Subsection of a Request or Response
As well as documenting a subsection of a request or response body, you can also document
the fields in a particular subsection. You can produce a snippet that documents the
fields of the temperature
object (high
and low
) as follows:
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("location",
responseFields(beneathPath("weather.temperature"), (1)
fieldWithPath("high").description(
"The forecast high in degrees celcius"), (2)
fieldWithPath("low")
.description("The forecast low in degrees celcius"))));
1 | Produce a snippet describing the fields in the subsection of the response payload
beneath the path weather.temperature . Uses the static beneathPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Document the high and low fields. |
this.webTestClient.get().uri("/locations/1").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("temperature",
responseFields(beneathPath("weather.temperature"), (1)
fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
fieldWithPath("low").description("The forecast low in degrees celcius"))));
1 | Produce a snippet describing the fields in the subsection of the response payload
beneath the path weather.temperature . Uses the static beneathPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Document the high and low fields. |
RestAssured.given(this.spec).accept("application/json")
.filter(document("location", responseFields(beneathPath("weather.temperature"), (1)
fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
fieldWithPath("low").description("The forecast low in degrees celcius"))))
.when().get("/locations/1")
.then().assertThat().statusCode(is(200));
1 | Produce a snippet describing the fields in the subsection of the response payload
beneath the path weather.temperature . Uses the static beneathPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
2 | Document the high and low fields. |
The result is a snippet that contains a table describing the high
and low
fields of
weather.temperature
. To make the snippet’s name distinct, an identifier for the
subsection is included. By default, this identifier is beneath-${path}
. For example,
the preceding code results in a snippet named
response-fields-beneath-weather.temperature.adoc
.
Request Parameters
You can document a request’s parameters by using requestParameters
. You can include
request parameters in a GET
request’s query string. The following examples show how to
do so:
this.mockMvc.perform(get("/users?page=2&per_page=100")) (1)
.andExpect(status().isOk())
.andDo(document("users", requestParameters( (2)
parameterWithName("page").description("The page to retrieve"), (3)
parameterWithName("per_page").description("Entries per page") (4)
)));
1 | Perform a GET request with two parameters, page and per_page , in the query
string. |
2 | Configure Spring REST Docs to produce a snippet describing the request’s parameters.
Uses the static requestParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the page parameter. Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
4 | Document the per_page parameter. |
this.webTestClient.get().uri("/users?page=2&per_page=100") (1)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("users", requestParameters( (2)
parameterWithName("page").description("The page to retrieve"), (3)
parameterWithName("per_page").description("Entries per page") (4)
)));
1 | Perform a GET request with two parameters, page and per_page , in the query
string. |
2 | Configure Spring REST Docs to produce a snippet describing the request’s parameters.
Uses the static requestParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the page parameter. Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
4 | Document the per_page parameter. |
RestAssured.given(this.spec)
.filter(document("users", requestParameters( (1)
parameterWithName("page").description("The page to retrieve"), (2)
parameterWithName("per_page").description("Entries per page")))) (3)
.when().get("/users?page=2&per_page=100") (4)
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST Docs to produce a snippet describing the request’s parameters.
Uses the static requestParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
2 | Document the page parameter. Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the per_page parameter. |
4 | Perform a GET request with two parameters, page and per_page , in the query
string. |
You can also include request parameters as form data in the body of a POST request. The following examples show how to do so:
this.mockMvc.perform(post("/users").param("username", "Tester")) (1)
.andExpect(status().isCreated())
.andDo(document("create-user", requestParameters(
parameterWithName("username").description("The user's username")
)));
1 | Perform a POST request with a single parameter, username . |
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "Tester");
this.webTestClient.post().uri("/users").body(BodyInserters.fromFormData(formData)) (1)
.exchange().expectStatus().isCreated().expectBody()
.consumeWith(document("create-user", requestParameters(
parameterWithName("username").description("The user's username")
)));
1 | Perform a POST request with a single parameter, username . |
RestAssured.given(this.spec)
.filter(document("create-user", requestParameters(
parameterWithName("username").description("The user's username"))))
.formParam("username", "Tester") (1)
.when().post("/users") (2)
.then().assertThat().statusCode(is(200));
1 | Configure the username parameter. |
2 | Perform the POST request. |
In all cases, the result is a snippet named request-parameters.adoc
that contains a
table describing the parameters that are supported by the resource.
When documenting request parameters, the test fails if an undocumented request parameter is used in the request. Similarly, the test also fails if a documented request parameter is not found in the request and the request parameter has not been marked as optional.
If you do not want to document a request parameter, you can mark it as ignored. This prevents it from appearing in the generated snippet while avoiding the failure described above.
You can also document request parameters in a relaxed mode where any undocumented
parameters do not cause a test failure. To do so, use the relaxedRequestParameters
method on org.springframework.restdocs.request.RequestDocumentation
. This can be useful
when documenting a particular scenario where you only want to focus on a subset of the
request parameters.
Path Parameters
You can document a request’s path parameters by using pathParameters
. The following
examples show how to do so:
this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) (1)
.andExpect(status().isOk())
.andDo(document("locations", pathParameters( (2)
parameterWithName("latitude").description("The location's latitude"), (3)
parameterWithName("longitude").description("The location's longitude") (4)
)));
1 | Perform a GET request with two path parameters, latitude and longitude . |
2 | Configure Spring REST Docs to produce a snippet describing the request’s path
parameters. Uses the static pathParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the parameter named latitude . Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
4 | Document the parameter named longitude . |
this.webTestClient.get().uri("/locations/{latitude}/{longitude}", 51.5072, 0.1275) (1)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("locations",
pathParameters( (2)
parameterWithName("latitude").description("The location's latitude"), (3)
parameterWithName("longitude").description("The location's longitude")))); (4)
1 | Perform a GET request with two path parameters, latitude and longitude . |
2 | Configure Spring REST Docs to produce a snippet describing the request’s path
parameters. Uses the static pathParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the parameter named latitude . Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
4 | Document the parameter named longitude . |
RestAssured.given(this.spec)
.filter(document("locations", pathParameters( (1)
parameterWithName("latitude").description("The location's latitude"), (2)
parameterWithName("longitude").description("The location's longitude")))) (3)
.when().get("/locations/{latitude}/{longitude}", 51.5072, 0.1275) (4)
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST Docs to produce a snippet describing the request’s path
parameters. Uses the static pathParameters method on
org.springframework.restdocs.request.RequestDocumentation . |
2 | Document the parameter named latitude . Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the parameter named longitude . |
4 | Perform a GET request with two path parameters, latitude and longitude . |
The result is a snippet named path-parameters.adoc
that contains a table describing
the path parameters that are supported by the resource.
If you use MockMvc, to make the path parameters available for documentation,
you must build the request by using one of the methods on
RestDocumentationRequestBuilders rather than MockMvcRequestBuilders .
|
When documenting path parameters, the test fails if an undocumented path parameter is used in the request. Similarly, the test also fails if a documented path parameter is not found in the request and the path parameter has not been marked as optional.
You can also document path parameters in a relaxed mode, where any undocumented
parameters do not cause a test failure. To do so, use the relaxedPathParameters
method
on org.springframework.restdocs.request.RequestDocumentation
. This can be useful when
documenting a particular scenario where you only want to focus on a subset of the path
parameters.
If you do not want to document a path parameter, you can mark it as ignored. Doing so prevents it from appearing in the generated snippet while avoiding the failure described earlier.
Request Parts
You can use requestParts
to document the parts of a multipart request. The following
example shows how to do so:
this.mockMvc.perform(multipart("/upload").file("file", "example".getBytes())) (1)
.andExpect(status().isOk())
.andDo(document("upload", requestParts( (2)
partWithName("file").description("The file to upload")) (3)
));
1 | Perform a POST request with a single part named file . |
2 | Configure Spring REST Docs to produce a snippet describing the request’s parts. Uses
the static requestParts method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the part named file . Uses the static partWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
multipartData.add("file", "example".getBytes());
this.webTestClient.post().uri("/upload").body(BodyInserters.fromMultipartData(multipartData)) (1)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("upload", requestParts( (2)
partWithName("file").description("The file to upload")) (3)
));
1 | Perform a POST request with a single part named file . |
2 | Configure Spring REST Docs to produce a snippet describing the request’s parts. Uses
the static requestParts method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Document the part named file . Uses the static partWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
RestAssured.given(this.spec)
.filter(document("users", requestParts( (1)
partWithName("file").description("The file to upload")))) (2)
.multiPart("file", "example") (3)
.when().post("/upload") (4)
.then().statusCode(is(200));
1 | Configure Spring REST Docs to produce a snippet describing the request’s parts. Uses
the static requestParts method on
org.springframework.restdocs.request.RequestDocumentation . |
2 | Document the part named file . Uses the static partWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Configure the request with the part named file . |
4 | Perform the POST request to /upload . |
The result is a snippet named request-parts.adoc
that contains a table describing the
request parts that are supported by the resource.
When documenting request parts, the test fails if an undocumented part is used in the request. Similarly, the test also fails if a documented part is not found in the request and the part has not been marked as optional.
You can also document request parts in a relaxed mode where any undocumented parts do not
cause a test failure. To do so, use the relaxedRequestParts
method on
org.springframework.restdocs.request.RequestDocumentation
. This can be useful
when documenting a particular scenario where you only want to focus on a subset of the
request parts.
If you do not want to document a request part, you can mark it as ignored. This prevents it from appearing in the generated snippet while avoiding the failure described earlier.
Request Part Payloads
You can document the payload of a request part in much the same way as the payload of a request, with support for documenting a request part’s body and its fields.
Documenting a Request Part’s Body
You can generate a snippet containing the body of a request part as follows:
MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png",
"<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "",
"application/json", "{ \"version\": \"1.0\"}".getBytes());
this.mockMvc.perform(fileUpload("/images").file(image).file(metadata)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("image-upload", requestPartBody("metadata"))); (1)
1 | Configure Spring REST docs to produce a snippet containing the body of the request
part named metadata . Uses the static requestPartBody method on
PayloadDocumentation . |
MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
Resource imageResource = new ByteArrayResource("<<png data>>".getBytes()) {
@Override
public String getFilename() {
return "image.png";
}
};
multipartData.add("image", imageResource);
multipartData.add("metadata", Collections.singletonMap("version", "1.0"));
this.webTestClient.post().uri("/images").body(BodyInserters.fromMultipartData(multipartData))
.accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isOk().expectBody()
.consumeWith(document("image-upload",
requestPartBody("metadata"))); (1)
1 | Configure Spring REST docs to produce a snippet containing the body of the request
part named metadata . Uses the static requestPartBody method on
PayloadDocumentation . |
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
.filter(document("image-upload", requestPartBody("metadata"))) (1)
.when().multiPart("image", new File("image.png"), "image/png")
.multiPart("metadata", metadata).post("images")
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST docs to produce a snippet containing the body of the request
part named metadata . Uses the static requestPartBody method on
PayloadDocumentation . |
The result is a snippet named request-part-${part-name}-body.adoc
that contains the
part’s body. For example, documenting a part named metadata
produces a snippet named
request-part-metadata-body.adoc
.
Documenting a Request Part’s Fields
You can document a request part’s fields in much the same way as the fields of a request or response, as follows:
MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png",
"<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "",
"application/json", "{ \"version\": \"1.0\"}".getBytes());
this.mockMvc.perform(fileUpload("/images").file(image).file(metadata)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("image-upload", requestPartFields("metadata", (1)
fieldWithPath("version").description("The version of the image")))); (2)
1 | Configure Spring REST docs to produce a snippet describing the fields in the payload
of the request part named metadata . Uses the static requestPartFields method on
PayloadDocumentation . |
2 | Expect a field with the path version . Uses the static fieldWithPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
Resource imageResource = new ByteArrayResource("<<png data>>".getBytes()) {
@Override
public String getFilename() {
return "image.png";
}
};
multipartData.add("image", imageResource);
multipartData.add("metadata", Collections.singletonMap("version", "1.0"));
this.webTestClient.post().uri("/images").body(BodyInserters.fromMultipartData(multipartData))
.accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isOk().expectBody()
.consumeWith(document("image-upload",
requestPartFields("metadata", (1)
fieldWithPath("version").description("The version of the image")))); (2)
1 | Configure Spring REST docs to produce a snippet describing the fields in the payload
of the request part named metadata . Uses the static requestPartFields method on
PayloadDocumentation . |
2 | Expect a field with the path version . Uses the static fieldWithPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
.filter(document("image-upload", requestPartFields("metadata", (1)
fieldWithPath("version").description("The version of the image")))) (2)
.when().multiPart("image", new File("image.png"), "image/png")
.multiPart("metadata", metadata).post("images")
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST docs to produce a snippet describing the fields in the payload
of the request part named metadata . Uses the static requestPartFields method on
PayloadDocumentation . |
2 | Expect a field with the path version . Uses the static fieldWithPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
The result is a snippet that contains a table describing the part’s fields. This snippet
is named request-part-${part-name}-fields.adoc
. For example, documenting a part named
metadata
produces a snippet named request-part-metadata-fields.adoc
.
When documenting fields, the test fails if an undocumented field is found in the payload of the part. Similarly, the test also fails if a documented field is not found in the payload of the part and the field has not been marked as optional. For payloads with a hierarchical structure, documenting a field is sufficient for all of its descendants to also be treated as having been documented.
If you do not want to document a field, you can mark it as ignored. Doing so prevents it from appearing in the generated snippet while avoiding the failure described above.
You can also document fields in a relaxed mode, where any undocumented fields do not
cause a test failure. To do so, use the relaxedRequestPartFields
method on
org.springframework.restdocs.payload.PayloadDocumentation
. This can be useful when
documenting a particular scenario where you only want to focus on a subset of the payload
of the part.
For further information on describing fields, documenting payloads that use XML, and more, see the section on documenting request and response payloads.
HTTP Headers
You can document the headers in a request or response by using requestHeaders
and
responseHeaders
, respectively. The following examples show how to do so:
this.mockMvc
.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) (1)
.andExpect(status().isOk())
.andDo(document("headers",
requestHeaders( (2)
headerWithName("Authorization").description(
"Basic auth credentials")), (3)
responseHeaders( (4)
headerWithName("X-RateLimit-Limit").description(
"The total number of requests permitted per period"),
headerWithName("X-RateLimit-Remaining").description(
"Remaining requests permitted in current period"),
headerWithName("X-RateLimit-Reset").description(
"Time at which the rate limit period will reset"))));
1 | Perform a GET request with an Authorization header that uses basic authentication. |
2 | Configure Spring REST Docs to produce a snippet describing the request’s headers.
Uses the static requestHeaders method on
org.springframework.restdocs.headers.HeaderDocumentation . |
3 | Document the Authorization header. Uses the static headerWithName method on
org.springframework.restdocs.headers.HeaderDocumentation . |
4 | Produce a snippet describing the response’s headers. Uses the static responseHeaders
method on org.springframework.restdocs.headers.HeaderDocumentation . |
this.webTestClient
.get().uri("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=") (1)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("headers",
requestHeaders( (2)
headerWithName("Authorization").description("Basic auth credentials")), (3)
responseHeaders( (4)
headerWithName("X-RateLimit-Limit")
.description("The total number of requests permitted per period"),
headerWithName("X-RateLimit-Remaining")
.description("Remaining requests permitted in current period"),
headerWithName("X-RateLimit-Reset")
.description("Time at which the rate limit period will reset"))));
1 | Perform a GET request with an Authorization header that uses basic authentication. |
2 | Configure Spring REST Docs to produce a snippet describing the request’s headers.
Uses the static requestHeaders method on
org.springframework.restdocs.headers.HeaderDocumentation . |
3 | Document the Authorization header. Uses the static headerWithName method on
org.springframework.restdocs.headers.HeaderDocumentation . |
4 | Produce a snippet describing the response’s headers. Uses the static responseHeaders
method on org.springframework.restdocs.headers.HeaderDocumentation . |
RestAssured.given(this.spec)
.filter(document("headers",
requestHeaders( (1)
headerWithName("Authorization").description(
"Basic auth credentials")), (2)
responseHeaders( (3)
headerWithName("X-RateLimit-Limit").description(
"The total number of requests permitted per period"),
headerWithName("X-RateLimit-Remaining").description(
"Remaining requests permitted in current period"),
headerWithName("X-RateLimit-Reset").description(
"Time at which the rate limit period will reset"))))
.header("Authorization", "Basic dXNlcjpzZWNyZXQ=") (4)
.when().get("/people")
.then().assertThat().statusCode(is(200));
1 | Configure Spring REST Docs to produce a snippet describing the request’s headers.
Uses the static requestHeaders method on
org.springframework.restdocs.headers.HeaderDocumentation . |
2 | Document the Authorization header. Uses the static headerWithName method on
`org.springframework.restdocs.headers.HeaderDocumentation. |
3 | Produce a snippet describing the response’s headers. Uses the static responseHeaders
method on org.springframework.restdocs.headers.HeaderDocumentation . |
4 | Configure the request with an Authorization header that uses basic authentication. |
The result is a snippet named request-headers.adoc
and a snippet named
response-headers.adoc
. Each contains a table describing the headers.
When documenting HTTP Headers, the test fails if a documented header is not found in the request or response.
Reusing Snippets
It is common for an API that is being documented to have some features that are common
across several of its resources. To avoid repetition when documenting such resources, you
can reuse a Snippet
configured with the common elements.
First, create the Snippet
that describes the common elements. The following example
shows how to do so:
protected final LinksSnippet pagingLinks = links(
linkWithRel("first").optional().description("The first page of results"),
linkWithRel("last").optional().description("The last page of results"),
linkWithRel("next").optional().description("The next page of results"),
linkWithRel("prev").optional().description("The previous page of results"));
Second, use this snippet and add further descriptors that are resource-specific. The following examples show how to do so:
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("example", this.pagingLinks.and( (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | Reuse the pagingLinks Snippet , calling and to add descriptors that are specific
to the resource that is being documented. |
this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isOk().expectBody()
.consumeWith(document("example", this.pagingLinks.and( (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | Reuse the pagingLinks Snippet , calling and to add descriptors that are specific
to the resource that is being documented. |
RestAssured.given(this.spec)
.accept("application/json")
.filter(document("example", this.pagingLinks.and( (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))))
.get("/").then().assertThat().statusCode(is(200));
1 | Reuse the pagingLinks Snippet , calling and to add descriptors that are specific
to the resource that is being documented. |
The result of the example is that links with rel
values of first
, last
, next
,
previous
, alpha
, and bravo
are all documented.
Documenting Constraints
Spring REST Docs provides a number of classes that can help you to document constraints.
You can use an instance of ConstraintDescriptions
to access descriptions of a class’s
constraints. The following example shows how to do so:
public void example() {
ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class); (1)
List<String> descriptions = userConstraints.descriptionsForProperty("name"); (2)
}
static class UserInput {
@NotNull
@Size(min = 1)
String name;
@NotNull
@Size(min = 8)
String password;
}
1 | Create an instance of ConstraintDescriptions for the UserInput class. |
2 | Get the descriptions of the name property’s constraints. This list contains two
descriptions: one for the NotNull constraint and one for the Size constraint. |
The ApiDocumentation
class in the Spring HATEOAS sample shows this functionality in action.
Finding Constraints
By default, constraints are found by using a Bean Validation Validator
. Currently, only
property constraints are supported. You can customize the Validator
that is used by
creating ConstraintDescriptions
with a custom ValidatorConstraintResolver
instance.
To take complete control of constraint resolution, you can use your own implementation of
ConstraintResolver
.
Describing Constraints
Default descriptions are provided for all of Bean Validation 2.0’s constraints:
-
AssertFalse
-
AssertTrue
-
DecimalMax
-
DecimalMin
-
Digits
-
Email
-
Future
-
FutureOrPresent
-
Max
-
Min
-
Negative
-
NegativeOrZero
-
NotBlank
-
NotEmpty
-
NotNull
-
Null
-
Past
-
PastOrPresent
-
Pattern
-
Positive
-
PositiveOrZero
-
Size
Default descriptions are also provided for the following constraints from Hibernate Validator:
-
CodePointLength
-
CreditCardNumber
-
Currency
-
EAN
-
Email
-
Length
-
LuhnCheck
-
Mod10Check
-
Mod11Check
-
NotBlank
-
NotEmpty
-
Currency
-
Range
-
SafeHtml
-
URL
To override the default descriptions or to provide a new description, you can create a
resource bundle with a base name of
org.springframework.restdocs.constraints.ConstraintDescriptions
. The Spring
HATEOAS-based sample contains
an
example of such a resource bundle.
Each key in the resource bundle is the fully-qualified name of a constraint plus a
.description
. For example, the key for the standard @NotNull
constraint is
javax.validation.constraints.NotNull.description
.
You can use a property placeholder referring to a constraint’s attributes in its
description. For example, the default description of the @Min
constraint,
Must be at least ${value}
, refers to the constraint’s value
attribute.
To take more control of constraint description resolution, you can create
ConstraintDescriptions
with a custom ResourceBundleConstraintDescriptionResolver
. To
take complete control, you can create ConstraintDescriptions
with a custom
ConstraintDescriptionResolver
implementation.
Using Constraint Descriptions in Generated Snippets
Once you have a constraint’s descriptions, you are free to use them however you like in
the generated snippets. For example, you may want to include the constraint descriptions
as part of a field’s description. Alternatively, you could include the constraints as
extra information in
the request fields snippet. The
ApiDocumentation
class in the Spring HATEOAS-based sample illustrates the latter approach.
Default Snippets
A number of snippets are produced automatically when you document a request and response.
Snippet | Description |
---|---|
|
Contains the |
|
Contains the |
|
Contains the HTTP request that is equivalent to the |
|
Contains the HTTP response that was returned. |
|
Contains the body of the request that was sent. |
|
Contains the body of the response that was returned. |
You can configure which snippets are produced by default. See the configuration section for more information.
Using Parameterized Output Directories
When using MockMvc or REST Assured, you can parameterize the output directory used by
document
. You cannot parameterize the output directory when using WebTestClient
.
The following parameters are supported:
Parameter | Description |
---|---|
{methodName} |
The unmodified name of the test method. |
{method-name} |
The name of the test method, formatted using kebab-case. |
{method_name} |
The name of the test method, formatted using snake_case. |
{ClassName} |
The unmodified simple name of the test class. |
{class-name} |
The simple name of the test class, formatted using kebab-case. |
{class_name} |
The simple name of the test class, formatted using snake_case. |
{step} |
The count of calls made to the service in the current test. |
For example, document("{class-name}/{method-name}")
in a test method named
creatingANote
on the test class GettingStartedDocumentation
writes
snippets into a directory named getting-started-documentation/creating-a-note
.
A parameterized output directory is particularly useful in combination with a @Before
method. It lets documentation be configured once in a setup method and then reused
in every test in the class. The following examples show how to do so:
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}/{step}/")).build();
}
@Before
public void setUp() {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation))
.addFilter(document("{method-name}/{step}")).build();
}
With this configuration in place, every call to the service you are testing produces
the default snippets without any further
configuration. Take a look at the GettingStartedDocumentation
classes in each of the
sample applications to see this functionality in action.
Customizing the Output
This section describes how to customize the output of Spring REST Docs.
Customizing the Generated Snippets
Spring REST Docs uses Mustache templates to produce the generated snippets. Default templates are provided for each of the snippets that Spring REST Docs can produce. To customize a snippet’s content, you can provide your own template.
Templates are loaded from the classpath from an org.springframework.restdocs.templates
subpackage. The name of the subpackage is determined by the ID of the template format
that is in use. The default template format, Asciidoctor, has an ID of asciidoctor
, so
snippets are loaded from org.springframework.restdocs.templates.asciidoctor
. Each
template is named after the snippet that it produces. For example, to override the
template for the curl-request.adoc
snippet, create a template named
curl-request.snippet
in
src/test/resources/org/springframework/restdocs/templates/asciidoctor
.
Including Extra Information
There are two ways to provide extra information for inclusion in a generated snippet:
-
Use the
attributes
method on a descriptor to add one or more attributes to it. -
Pass in some attributes when calling
curlRequest
,httpRequest
,httpResponse
, and so on. Such attributes are associated with the snippet as a whole.
Any additional attributes are made available during the template rendering process. Coupled with a custom snippet template, this makes it possible to include extra information in a generated snippet.
A concrete example is the addition of a constraints column and a title when documenting
request fields. The first step is to provide a constraints
attribute for each field
that you document and to provide a title
attribute. The following examples show how to
do so:
.andDo(document("create-user", requestFields(
attributes(key("title").value("Fields for user creation")), (1)
fieldWithPath("name").description("The user's name")
.attributes(key("constraints")
.value("Must not be null. Must not be empty")), (2)
fieldWithPath("email").description("The user's email address")
.attributes(key("constraints")
.value("Must be a valid email address"))))); (3)
1 | Configure the title attribute for the request fields snippet. |
2 | Set the constraints attribute for the name field. |
3 | Set the constraints attribute for the email field. |
.consumeWith(document("create-user",
requestFields(
attributes(key("title").value("Fields for user creation")), (1)
fieldWithPath("name")
.description("The user's name")
.attributes(key("constraints").value("Must not be null. Must not be empty")), (2)
fieldWithPath("email")
.description("The user's email address")
.attributes(key("constraints").value("Must be a valid email address"))))); (3)
1 | Configure the title attribute for the request fields snippet. |
2 | Set the constraints attribute for the name field. |
3 | Set the constraints attribute for the email field. |
.filter(document("create-user", requestFields(
attributes(key("title").value("Fields for user creation")), (1)
fieldWithPath("name").description("The user's name")
.attributes(key("constraints")
.value("Must not be null. Must not be empty")), (2)
fieldWithPath("email").description("The user's email address")
.attributes(key("constraints")
.value("Must be a valid email address"))))) (3)
1 | Configure the title attribute for the request fields snippet. |
2 | Set the constraints attribute for the name field. |
3 | Set the constraints attribute for the email field. |
The second step is to provide a custom template named request-fields.snippet
that
includes the information about the fields' constraints in the generated snippet’s table
and adds a title. The following example shows how to do so:
.{{title}} (1)
|===
|Path|Type|Description|Constraints (2)
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{constraints}} (3)
{{/fields}}
|===
1 | Add a title to the table. |
2 | Add a new column named "Constraints". |
3 | Include the descriptors' constraints attribute in each row of the table. |
Customizing requests and responses
There may be situations where you do not want to document a request exactly as it was sent or a response exactly as it was received. Spring REST Docs provides a number of preprocessors that can be used to modify a request or response before it is documented.
Preprocessing is configured by calling document
with an OperationRequestPreprocessor
or an OperationResponsePreprocessor
. You can obtain instances by using the static
preprocessRequest
and preprocessResponse
methods on Preprocessors
. The following
examples show how to do so:
this.mockMvc.perform(get("/")).andExpect(status().isOk())
.andDo(document("index", preprocessRequest(removeHeaders("Foo")), (1)
preprocessResponse(prettyPrint()))); (2)
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
this.webTestClient.get().uri("/").exchange().expectStatus().isOk().expectBody()
.consumeWith(document("index",
preprocessRequest(removeHeaders("Foo")), (1)
preprocessResponse(prettyPrint()))); (2)
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
RestAssured.given(this.spec)
.filter(document("index", preprocessRequest(removeHeaders("Foo")), (1)
preprocessResponse(prettyPrint()))) (2)
.when().get("/")
.then().assertThat().statusCode(is(200));
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
Alternatively, you may want to apply the same preprocessors to every test. You can do so
by using the RestDocumentationConfigurer
API in your @Before
method to configure the
preprocessors. For example to remove the Foo
header from all requests and pretty print
all responses, you could do one of the following (depending on your testing environment):
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
}
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
private WebTestClient webTestClient;
@Before
public void setup() {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.filter(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
}
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
private RequestSpecification spec;
@Before
public void setup() {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
}
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
Then, in each test, you can perform any configuration specific to that test. The following examples show how to do so:
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(document("index",
links(linkWithRel("self").description("Canonical self link"))
));
this.webTestClient.get().uri("/").exchange().expectStatus().isOk()
.expectBody().consumeWith(document("index",
links(linkWithRel("self").description("Canonical self link"))));
RestAssured.given(this.spec)
.filter(document("index",
links(linkWithRel("self").description("Canonical self link"))))
.when().get("/")
.then().assertThat().statusCode(is(200));
Various built-in preprocessors, including those illustrated above, are available through
the static methods on Preprocessors
. See below for further details.
Preprocessors
Pretty Printing
prettyPrint
on Preprocessors
formats the content of the request or response
to make it easier to read.
Masking Links
If you are documenting a hypermedia-based API, you may want to encourage clients to
navigate the API by using links rather than through the use of hard coded URIs. One way to
do so is to limit the use of URIs in the documentation. maskLinks
on
Preprocessors
replaces the href
of any links in the response with …
. You can also
specify a different replacement if you wish.
Removing Headers
removeHeaders
on Preprocessors
removes any headers from the request or response where
the name is equal to any of the given header names.
removeMatchingHeaders
on Preprocessors
removes any headers from the request or
response where the name matches any of the given regular expression patterns.
Replacing Patterns
replacePattern
on Preprocessors
provides a general purpose mechanism for
replacing content in a request or response. Any occurrences that match a regular
expression are replaced.
Modifying Request Parameters
You can use modifyParameters
on Preprocessors
to add, set, and remove request
parameters.
Modifying URIs
If you use MockMvc or a WebTestClient that is not bound to a server, you should customize URIs by changing the configuration. |
You can use modifyUris
on Preprocessors
to modify any URIs in a request
or a response. When using REST Assured or WebTestClient bound to a server, this
lets you customize the URIs that appear in the documentation while testing a
local instance of the service.
Writing Your Own Preprocessor
If one of the built-in preprocessors does not meet your needs, you can write your own by
implementing the OperationPreprocessor
interface. You can then use your custom
preprocessor in exactly the same way as any of the built-in preprocessors.
If you want to modify only the content (body) of a request or response, consider
implementing the ContentModifier
interface and using it with the built-in
ContentModifyingOperationPreprocessor
.
Configuration
This section covers how to configure Spring REST Docs.
Documented URIs
This section covers configuring documented URIs.
MockMvc URI Customization
When using MockMvc, the default configuration for URIs documented by Spring REST Docs is as follows:
Setting | Default |
---|---|
Scheme |
|
Host |
|
Port |
|
This configuration is applied by MockMvcRestDocumentationConfigurer
. You can use its
API to change one or more of the defaults to suit your needs. The following example shows
how to do so:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).uris()
.withScheme("https")
.withHost("example.com")
.withPort(443))
.build();
If the port is set to the default for the configured scheme (port 80 for HTTP or port 443 for HTTPS), it is omitted from any URIs in the generated snippets. |
To configure a request’s context path, use the contextPath method on
MockHttpServletRequestBuilder .
|
REST Assured URI Customization
REST Assured tests a service by making actual HTTP requests. As a result, URIs must be customized once the operation on the service has been performed but before it is documented. A REST-Assured-specific preprocessor is provided for this purpose.
WebTestClient URI Customization
When using WebTestClient, the default base for URIs documented by Spring REST
Docs is http://localhost:8080
. You can customize this base by using the
baseUrl(String)
method on WebTestClient.Builder
. The following example shows how to
do so:
@Before
public void setUp() {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.baseUrl("https://api.example.com") (1)
.filter(documentationConfiguration(this.restDocumentation)).build();
}
1 | Configure the base of documented URIs to be https://api.example.com . |
Snippet Encoding
The default snippet encoding is UTF-8
. You can change the default snippet encoding by
using the RestDocumentationConfigurer
API. For example, the following examples use
ISO-8859-1
:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withEncoding("ISO-8859-1"))
.build();
this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
.filter(documentationConfiguration(this.restDocumentation)
.snippets().withEncoding("ISO-8859-1"))
.build();
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation)
.snippets().withEncoding("ISO-8859-1"))
.build();
When Spring REST Docs converts the content of a request or a response to a String ,
the charset specified in the Content-Type header is used if it is available. In its
absence, the JVM’s default Charset is used. You can configure the JVM’s default
Charset byusing the file.encoding system property.
|
Snippet Template Format
The default snippet template format is Asciidoctor. Markdown is also supported out of the
box. You can change the default format by using the RestDocumentationConfigurer
API.
The following examples show how to do so:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withTemplateFormat(TemplateFormats.markdown()))
.build();
this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
.filter(documentationConfiguration(this.restDocumentation)
.snippets().withTemplateFormat(TemplateFormats.markdown()))
.build();
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation)
.snippets().withTemplateFormat(TemplateFormats.markdown()))
.build();
Default Snippets
Six snippets are produced by default:
-
curl-request
-
http-request
-
http-response
-
httpie-request
-
request-body
-
response-body
You can change the default snippet configuration during setup by using the
RestDocumentationConfigurer
API. The following examples produce only the curl-request
snippet by default:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient().filter(
documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.build();
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
Default Operation Preprocessors
You can configure default request and response preprocessors during setup by using the
RestDocumentationConfigurer
API. The following examples remove the Foo
headers from
all requests and pretty print all responses:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.filter(documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
.withRequestDefaults(removeHeaders("Foo")) (1)
.withResponseDefaults(prettyPrint())) (2)
.build();
1 | Apply a request preprocessor that removes the header named Foo . |
2 | Apply a response preprocessor that pretty prints its content. |
Working with Asciidoctor
This section describes the aspects of working with Asciidoctor that are particularly relevant to Spring REST Docs.
Asciidoc is the document format. Asciidoctor is the tool that produces content
(usually as HTML) from Asciidoc files (which end with .adoc ).
|
Including Snippets
This section covers how to include Asciidoc snippets.
Including Multiple Snippets for an Operation
You can use a macro named operation
to import all or some of the snippets that have
been generated for a specific operation. It is made available by including
spring-restdocs-asciidoctor
in your project’s build configuration.
The target of the macro is the name of the operation. In its simplest form, you can use the macro to include all of the snippets for an operation, as shown in the following example:
operation::index[]
You can use the operation macro also supports a snippets
attribute. The snippets
attribute to select the snippets that should be included. The attribute’s value is a
comma-separated list. Each entry in the list should be the name of a snippet file (minus
the .adoc
suffix) to include. For example, only the curl, HTTP request and HTTP response
snippets can be included, as shown in the following example:
operation::index[snippets='curl-request,http-request,http-response']
The preceding example is the equivalent of the following:
[[example_curl_request]]
== Curl request
include::{snippets}/index/curl-request.adoc[]
[[example_http_request]]
== HTTP request
include::{snippets}/index/http-request.adoc[]
[[example_http_response]]
== HTTP response
include::{snippets}/index/http-response.adoc[]
Section Titles
For each snippet that is included by using the operation
macro, a section with a title
is created. Default titles are provided for the following built-in snippets:
Snippet | Title |
---|---|
|
Curl Request |
|
HTTP request |
|
HTTP response |
|
HTTPie request |
|
Links |
|
Request body |
|
Request fields |
|
Response body |
|
Response fields |
For snippets not listed in the preceding table, a default title is generated by replacing
-
characters with spaces and capitalizing the first letter. For example, the title for a
snippet named custom-snippet
will be
“Custom snippet”.
You can customize the default titles by using document attributes. The name of the
attribute should be operation-{snippet}-title
. For example, to customize the title of
the curl-request
snippet to be "Example request", you can use the following attribute:
:operation-curl-request-title: Example request
Including Individual Snippets
The include
macro is used to include individual snippets in your documentation. You can use the
snippets
attribute (which is automatically set by spring-restdocs-asciidoctor
configured in the build configuration) to
reference the snippets output directory. The following example shows how to do so:
include::{snippets}/index/curl-request.adoc[]
Customizing Tables
Many of the snippets contain a table in its default configuration. The appearance of the table can be customized, either by providing some additional configuration when the snippet is included or by using a custom snippet template.
Formatting Columns
Asciidoctor has rich support for
formatting a table’s columns. As
the following example shows, you can specify the widths of a table’s columns by using the
cols
attribute:
[cols="1,3"] (1)
include::{snippets}/index/links.adoc[]
1 | The table’s width is split across its two columns, with the second column being three times as wide as the first. |
Configuring the Title
You can specify the title of a table by using a line prefixed by a .
.
The following example shows how to do so:
.Links (1)
include::{snippets}/index/links.adoc[]
1 | The table’s title will be Links . |
Avoiding Table Formatting Problems
Asciidoctor uses the |
character to delimit cells in a table. This can cause problems
if you want a |
to appear in a cell’s contents. You can avoid the problem by
escaping the |
with a backslash — in other words, by using \|
rather than |
.
All of the default Asciidoctor snippet templates perform this escaping automatically by
using a Mustache lamba named tableCellContent
. If you write your own custom templates
you may want to use this lamba. The following example shows how to escape |
characters
in a cell that contains the value of a description
attribute:
| {{#tableCellContent}}{{description}}{{/tableCellContent}}
Further Reading
See the Tables section of the Asciidoctor user manual for more information about customizing tables.
Working with Markdown
This section describes the aspects of working with Markdown that are particularly relevant to Spring REST Docs.
Limitations
Markdown was originally designed for people writing for the web and, as such, is not as well-suited to writing documentation as Asciidoctor. Typically, these limitations are overcome by using another tool that builds on top of Markdown.
Markdown has no official support for tables. Spring REST Docs' default Markdown snippet templates use Markdown Extra’s table format.
Including Snippets
Markdown has no built-in support for including one Markdown file in another. To include the generated snippets of Markdown in your documentation, you should use an additional tool that supports this functionality. One example that is particularly well-suited to documenting APIs is Slate.
Contributing
Spring REST Docs is intended to make it easy for you to produce high-quality documentation for your RESTful services. However, we cannot achieve that goal without your contributions.
Questions
You can ask questions about Spring REST Docs on Stack Overflow
by using the spring-restdocs
tag. Similarly, we encourage you to help your fellow
Spring REST Docs users by answering questions.
Bugs
If you believe you have found a bug, please take a moment to search the existing issues. If no one else has reported the problem, please open a new issue that describes the problem in detail and, ideally, includes a test that reproduces it.
Enhancements
If you would like an enhancement to be made to Spring REST Docs, pull requests are most welcome. The source code is on GitHub. You may want to search the existing issues and pull requests to see if the enhancement has already been proprosed. You may also want to open a new issue to discuss a possible enhancement before work on it begins.