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 to produce documentation for your RESTful services that is accurate and readable.
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. Asciidoctor processes plain text and produces HTML styled and layed out to suit your needs.
Spring REST Docs makes use of snippets produced by tests written with Spring MVC Test. This test-driven approach helps to guarantee the accuracy of your service’s documentation. If a snippet is incorrect the test that produces it will fail.
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 allows you to 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 to 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, there are two sample applications available. One sample uses Spring HATEOAS and the other uses Spring Data REST. Both samples use Spring REST Docs to produce a detailed API guide and a getting started walkthrough. You can use either Gradle or Maven to build them.
In each sample the source for the documentation can be found in src/main/asciidoc
.
api-guide.adoc
produces an API guide for the service. getting-started-guide.adoc
produces a getting started guide that provides an introductory walkthrough.
The code that produces the generated snippets can be found in src/test/java
.
ApiDocumentation.java
produces the snippets for the API guide.
GettingStartedDocumentation.java
produces the snippets for the getting started guide.
Build configuration
The first step in using Spring REST Docs is to configure your project’s build.
Gradle build configuration
Both sample applications contain build.gradle
files that you may wish to
use as a reference. The key parts of the configuration are described below.
plugins { (1)
id "org.asciidoctor.convert" version "1.5.2"
}
dependencies { (2)
testCompile 'org.springframework.restdocs:spring-restdocs:1.0.0.M1'
}
ext { (3)
snippetsDir = file('build/generated-snippets')
}
test { (4)
systemProperty 'org.springframework.restdocs.outputDir', snippetsDir
outputs.dir snippetsDir
}
asciidoctor { (5)
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
dependsOn test
}
1 | Apply the Asciidoctor plugin |
2 | Add a dependency on spring-restdocs in the testCompile configuration: |
3 | Configure a property to define the output location for generated snippets: |
4 | Configure the test task with the org.springframework.restdocs.outputDir system
property. This property controls the location into which Spring REST Docs will write the
snippets that it generates. |
5 | Configure the asciidoctor task and define an attribute named snippets . You can
then use this attribute when including the generated snippets in your documentation. |
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. You can do so by configuring the jar
task to depend on
the asciidoctor
task and to copy the generated documentation into the jar’s static
directory:
jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}
Maven build configuration
Both sample applications contain pom.xml
files that you may wish to
use as a reference. The key parts of the configuration are described below.
<dependency> (1)
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs</artifactId>
<version>1.0.0.M1</version>
<scope>test</scope>
</dependency>
<properties> (2)
<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
</properties>
<build>
<plugins>
<plugin> (3)
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Documentation.java</include>
</includes>
<systemPropertyVariables>
<org.springframework.restdocs.outputDir>
${snippetsDirectory}
</org.springframework.restdocs.outputDir>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin> (4)
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.2</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>package</phase> (5)
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<attributes>
<snippets>${snippetsDirectory}</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
1 | Add a dependency on spring-restdocs in the test scope |
2 | Configure a property to define the output location for generated snippets |
3 | Configure the SureFire plugin with the org.springframework.restdocs.outputDir system
property. This property controls the location into which Spring REST docs will write the
snippets that it generates. The plugin is also configured to include files whose names end
with Documentation.java : |
4 | Configure the Asciidoctor plugin and define an attribute named snippets . You can
then use this attribute when including the generated snippets in your documentation. |
5 | If you want to
package the documentation in your
project’s jar you should use the prepare-package phase. |
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.
First, configure the Asciidoctor plugin so that it runs in the prepare-package
phase, as
described above. Now configure
Maven’s resources plugin to copy the generated documentation into a location where it’ll
be included in the project’s jar:
<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>
<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 and the resource plugin must run after the Asciidoctor plugin. |
Generating documentation snippets
Spring REST Docs uses Spring’s MVC Test framework to make requests to the service that you are documenting. It then produces documentation snippets for the result’s request and response.
Setting up Spring MVC test
The first step in generating documentation snippets is to provide an @Before
method
that creates a MockMvc
instance:
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration())
.build();
}
The MockMvc
instance is configured using a RestDocumentationConfigurer
. An instance
of this class can be obtained from the static documentationConfiguration()
method on
org.springframework.restdocs.RestDocumentation
. RestDocumentationConfigurer
applies
sensible defaults and also provides an API for customizing the configuration. Refer to the
Configuration section for more information.
Invoking the RESTful service
Now that a MockMvc
instance has been created, it can be used to invoke the RESTful
service and document the request and response.
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) (1)
.andExpect(status().isOk()) (2)
.andDo(document("index")); (3)
1 | Invoke the root (/ ) of the service an indicate that an application/json response
is required |
2 | Assert that the service is produced the expected response |
3 | Document the call to the service, writing the snippets into a directory named index
that will be located beneath the configured output directory. The snippets are written by
a RestDocumentationResultHandler . An instance of this class can be obtained from the
static document method on org.springframework.restdocs.RestDocumentation . |
By default, three snippets a written:
-
<output-directory>/index/curl-request.adoc
-
<output-directory>/index/http-request.adoc
-
<output-directory>/index/http-response.adoc
Refer to Documenting your API for more information about these and other snippets that can be produced by Spring REST Docs.
Using the snippets
The generated snippets can be included in your documentation using the
include macro.
The snippets
attribute specified in the build
configuration can be used to reference the snippets output directory, for example:
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:
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index").withLinks( (1)
linkWithRel("alpha").description("Link to the alpha resource"), (2)
linkWithRel("bravo").description("Link to the bravo resource"))); (3)
1 | withLinks is used to describe the expected links |
2 | Expects a link whose rel is alpha . Uses the static linkWithRel method on
org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | Expects a link whose rel is bravo |
The result is a snippet named links.adoc
that contains a table describing the resource’s
links.
When documenting links, the test will fail if an undocumented link is found in the response. Similarly, the test will also fail if a documented link is not found in the response and the link has not be marked as optional.
Hypermedia link formats
Two link formats are understood by default:
-
Atom – links are expected to be in an array named
links
. 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
. Used by default when the content type of the response is compatible withapplication/hal+json
.
If you are using Atom or HAL-format links but with a different content type you can
provide one of the built-in LinkExtractor
implementations to withLinks
. For example:
.andDo(document("index").withLinks(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 using the halLinks static method on
org.springframework.restdocs.hypermedia.LinkExtractors . |
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.
Request and response payloads
In addition to the Hypermedia-specific support described above, support for general documentation of request and response payloads is also provided:
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index").withResponseFields( (1)
fieldWithPath("contact").description("The user's contact details"), (2)
fieldWithPath("contact.email").description("The user's email address"))); (3)
1 | withResponseFields is used to describe the expected fields in the response payload.
To document a request withRequestFields can be used. |
2 | Expects a field with the path contact . Uses the static fieldWithPath method on
org.springframework.restdocs.payload.PayloadDocumentation . |
3 | Expects a field with the path contact.email |
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 will fail if an undocumented field is found in the payload. Similarly, the test will also fail if a documented field is not found in the payload and the field has not be marked as optional. For payloads with a hierarchical structure, when a point in the hierarchy is documented any fields beneath that point are also deemed to have been documented. This means that you do not have to document the entire hierarchy, but you may do so if you wish.
Field paths
When documenting request and response payloads, fields are identified using a path. Paths
use .
to descend into a child object and []
to identify an array. For example, with
this JSON payload:
{
"a":{
"b":[
{
"c":"one"
},
{
"c":"two"
},
{
"d":"three"
}
]
}
}
The following paths are all present:
Path | Value |
---|---|
|
An object containing |
|
An array containing three objects |
|
An array containing three objects |
|
An array containing the strings |
|
The string |
Field types
When a field is documented, Spring REST docs will attempt to determine its type by examining the payload. Seven different types are supported:
Type | Description |
---|---|
array |
The value of each occurrence of the field is an array |
boolean |
The value of each occurrence of the field is a boolean ( |
object |
The value of each occurrence of the field is an object |
number |
The value of each occurrence of the field is a number |
null |
The value of each occurrence of the field is |
string |
The value of each occurrence of the field is a string |
varies |
The field occurs multiple times in the payload with a variety of different types |
The type can also be set explicitly using the type(FieldType)
method on
FieldDescriptor
:
.andDo(document("index").withResponseFields(
fieldWithPath("contact.email")
.type(FieldType.STRING) (1)
.optional()
.description("The user's email address")));
1 | Set the field’s type to string . |
Query parameters
A request’s query parameters can be documented using withQueryParameters
this.mockMvc.perform(get("/users?page=2&per_page=100"))
.andExpect(status().isOk())
.andDo(document("users").withQueryParameters( (1)
parameterWithName("page").description("The page to retrieve"), (2)
parameterWithName("per_page").description("Entries per page") (3)
));
1 | withQueryParameters is used to describe the query parameters |
2 | Documents a parameter named page . Uses the static parameterWithName method on
org.springframework.restdocs.request.RequestDocumentation . |
3 | Documents a parameter named per_page |
The result is a snippet named query-parameters.adoc
that contains a table describing
the query parameters that are supported by the resource.
When documenting query parameters, the test will fail if an undocumented query parameter is used in the request. Similarly, the test will also fail if a documented query parameter is not found in the request.
Default snippets
A number of snippets are produced automatically when you document a call to
MockMvc.perform
:
Snippet | Description |
---|---|
|
Contains the |
|
Contains the HTTP request that is equivalent to the |
|
Contains the HTTP response that was returned |
Using parameterized output directories
The output directory used by document
can be parameterized. The following parameters
are supported:
Parameter | Description |
---|---|
{methodName} |
The name of the test method, formatted using camelCase |
{method-name} |
The name of the test method, formatted using kebab-case |
{method_name} |
The name of the test method, formatted using snake_case |
{step} |
The count of calls to MockMvc.perform in the current test |
For example, document("{method-name}")
in a test method named creatingANote
will write
snippets into a directory named creating-a-note
.
The {step}
parameter is particularly useful in combination with Spring MVC Test’s
alwaysDo
functionality. It allows documentation to be configured once in a setup method:
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration())
.alwaysDo(document("{method-name}/{step}/"))
.build();
}
With this configuration in place, every call to MockMvc.perform
will produce
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 responses
There may be situations where you do not want to document a response exactly as received. Spring REST Docs provides a number of response post processors that can be used to modify a response after it is received but before it’s documented.
Response modification is configured using a ResponseModifier
. An instance can be
obtained using the static modifyResponseTo
method on RestDocumentation
. Once the
response modifications have been provided, documentation can be configured as usual
via the andDocument
method:
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(modifyResponseTo(/* ... */) (1)
.andDocument("index")); (2)
1 | Call modifyResponseTo to configure response modifications, passing in one or more
ResponsePostProcessor implementations. |
2 | Proceed with documenting the call |
Pretty printing
prettyPrintContent
on ResponsePostProcessors
formats the body of the response to
make it easier to read.
Masking links
If you’re documenting a Hypermedia-based API, you may want to encourage clients to
navigate the API using links rather than through the use of hard coded URIs. One way to do
this is to limit the use of URIs in the documentation. maskLinks
on
ResponsePostProcessors
replaces the href
of any links in the response with …
. A
different replacement can also be specified if you wish.
Removing headers
removeHeaders
on ResponsePostProcessors
removes any occurrences of the named headers
from the response.
Replacing patterns
replacePattern
on ResponsePostProcessors
provides a general purpose mechanism for
replacing content in a response. Any occurrences of a regular expression are replaced.
Configuration
Documented URIs
The default configuration for URIs documented by Spring REST docs is:
Setting | Default |
---|---|
Scheme |
|
Host |
|
Port |
|
Context path |
Empty string |
This configuration is applied by RestDocumentationConfigurer
. You can use its API to
change one or more of the defaults to suit your needs:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration().uris()
.withScheme("https")
.withHost("example.com")
.withPort(443)
.withContextPath("/api"))
.build();
Snippet encoding
The default encoding used by Asciidoc is UTF-8
. Spring REST docs adopts the same
default for the snippets that it generated. If you require an encoding other than UTF-8
,
use RestDocumentationConfigurer
to configure it:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration().snippets()
.withEncoding("ISO-8859-1"))
.build();
Snippet output directory
As described in Build configuration the snippet output directory is
configured in your pom.xml
or build.gradle
file. This configuration applies to builds
on the command line, but it may not apply when running your tests in your IDE. In the
absence of the property, Spring REST Docs will write the generated snippets to standard
out.
If you’d prefer that your IDE writes the snippets to disk you can use a file in
src/test/resources
named documentation.properties
to specify the output directory that
should be used:
org.springframework.restdocs.outputDir: target/generated-snippets
Working with Asciidoctor
This section describes any aspects of working with Asciidoctor that are particularly relevant to Spring REST Docs.
Including snippets
The include
macro is used to include generated snippets in your documentation. The snippets
attribute specified in the build configuration
can be used to reference the snippets output directory, for example:
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 by providing some additional configuration when the snippet is included.
Formatting columns
Asciidoctor has rich support for
formatting a table’s columns. For
example, the widths of a table’s columns can be specified using the cols
attribute:
[cols=1,3] (1)
include::{snippets}index/links.adoc[]
1 | The table’s width will be split across its two columns with the second column being three times as wide as the first. |
Configuring the title
The title of a table can be specified using a line prefixed by a .
:
.Links (1)
include::{snippets}index/links.adoc[]
1 | The table’s title will be Links . |
Further reading
Refer to the Tables section of the Asciidoctor user manual for more information about customizing tables.
Contributing
Spring REST Docs is intended to make is easy for you to produce high-quality documentation for your RESTful services. However, we can’t achieve that goal without your contributions.
Questions
You can ask questions about Spring REST Docs on StackOverflow
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’d 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 is already being worked on. You may also want to open a new issue to discuss a possible enhancement before work on it begins.