The aim of this extension to Spring REST Docs is to help you to write even less — both code and documentation. You still get the same nice documentation as with Spring REST Docs itself. The main benefit is that writing less and moving the documentation closer to the code increases the maintainability of the documentation.

In Spring REST Docs you have to add the documentation for your JSON with a DSL in your test. We moved this documentation to the POJO that represents your JSON object. You just add Javadoc to the fields and it will end up in the documentation.

Features:

  • Jackson visitor that gathers the whole JSON structure and includes Javadoc and constraint annotations on the fields. It works for both request and response bodies. In addition to the constraint documentation support that is already in Spring REST Docs, the constraint message is automatically included in the documentation. Constraint groups are also supported.

  • Path and query parameters can be documented automatically.

  • A helper to document authentication.

  • A snippet that includes all other snippets and thus helps you write even less.

FAQ

  1. Does it work with Spring MVC tests?

    Yes, it is easy to use in existing or new Spring MVC tests. Take a look at the example.

  2. Does it work with REST Assured tests?

    Not yet. We may add support for REST Assured in the future, but your PR is also welcome.

  3. Is Jackson required for automatic field documentation?

    Yes, this project only includes a Jackson visitor so far.

  4. Is a multi-module project setup supported?

    The JSON doclet and the REST Docs snippets produce files. Therefore, all results can be copied across modules or projects. Instead of copying, multiple source directories for the JSON files can be configured with the javadocJsonDir property. Directories are separated by , and are processed in order and only the first found JSON file is used.

Getting started

Requirements

Spring Auto REST Docs has the following minimum requirements:

  • Java 7

  • Spring REST Docs 1.2.1.RELEASE (see documentation)

  • Jackson has to be used for creating and parsing JSON

Usage

  1. Setup project for Spring REST Docs

  2. Additional configuration for this extension:

    Maven
    <dependency>
        <groupId>capital.scalable</groupId>
        <artifactId>spring-auto-restdocs-core</artifactId>
        <version>1.0.7</version>
        <scope>test</scope> (1)
    </dependency>
    
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                    <systemPropertyVariables>
                        <org.springframework.restdocs.outputDir>
                            ${snippetsDirectory}
                        </org.springframework.restdocs.outputDir>
                        <org.springframework.restdocs.javadocJsonDir>
                            ${project.build.directory}/generated-javadoc-json (2)
                        </org.springframework.restdocs.javadocJsonDir>
                    </systemPropertyVariables>
                 </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-javadoc-plugin</artifactId>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>generate-javadoc-json</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>javadoc-no-fork</goal>
                        </goals>
                        <configuration>
                            <doclet>capital.scalable.restdocs.jsondoclet.ExtractDocumentationAsJsonDoclet</doclet>
                            <docletArtifact>
                                <groupId>capital.scalable</groupId>
                                <artifactId>spring-auto-restdocs-json-doclet</artifactId>
                                <version>1.0.7</version>
                            </docletArtifact>
                            <destDir>generated-javadoc-json</destDir>
                            <reportOutputDirectory>${project.build.directory}</reportOutputDirectory>
                            <useStandardDocletOptions>false</useStandardDocletOptions>
                            <show>package</show>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            ...
        </plugins>
    </build>
    1 Has to be removed if @RestdocsNotExpanded is used.
    2 Multiple directories can be listed by separating them with ,. The directories are processed in order and only the first found JSON file is used.
    Gradle
    configurations {
        jsondoclet
    }
    
    ext {
        javadocJsonDir = file("$buildDir/generated-javadoc-json") (1)
    }
    
    dependencies {
        testCompile group: 'capital.scalable', name: 'spring-auto-restdocs-core', version: '1.0.7' (2)
        jsondoclet group: 'capital.scalable', name: 'spring-auto-restdocs-json-doclet', version: '1.0.7'
    }
    
    task jsonDoclet(type: Javadoc, dependsOn: compileJava) {
        source = sourceSets.main.allJava
        classpath = sourceSets.main.compileClasspath
        destinationDir = javadocJsonDir
        options.docletpath = configurations.jsondoclet.files.asType(List)
        options.doclet = 'capital.scalable.restdocs.jsondoclet.ExtractDocumentationAsJsonDoclet'
        options.memberLevel = JavadocMemberLevel.PACKAGE
    }
    
    test {
        systemProperty 'org.springframework.restdocs.outputDir', snippetsDir
        systemProperty 'org.springframework.restdocs.javadocJsonDir', javadocJsonDir
    
        dependsOn jsonDoclet
    }
    
    jar {
        dependsOn asciidoctor
    }
    1 Multiple directories can be listed by separating them with ,. The directories are processed in order and only the first found JSON file is used.
    2 Has to be compile instead of testCompile if @RestdocsNotExpanded is used.
  3. Configure MockMvc

    @Autowired
    private WebApplicationContext context;
    
    @Autowired
    protected ObjectMapper objectMapper;
    
    protected MockMvc mockMvc;
    
    @Rule
    public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(
        System.getProperties().getProperty("org.springframework.restdocs.outputDir"));
    
    @Before
    public void setUp() throws Exception {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .addFilters(springSecurityFilterChain)
                .alwaysDo(JacksonResultHandlers.prepareJackson(objectMapper))
                .alwaysDo(MockMvcRestDocumentation.document("{class-name}/{method-name}",
                        Preprocessors.preprocessRequest(),
                        Preprocessors.preprocessResponse(
                                ResponseModifyingPreprocessors.replaceBinaryContent(),
                                ResponseModifyingPreprocessors.limitJsonArrayLength(objectMapper),
                                Preprocessors.prettyPrint())))
                .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
                        .uris()
                        .withScheme("http")
                        .withHost("localhost")
                        .withPort(8080)
                        .and().snippets()
                        .withDefaults(CliDocumentation.curlRequest(),
                                HttpDocumentation.httpRequest(),
                                HttpDocumentation.httpResponse(),
                                AutoDocumentation.requestFields(),
                                AutoDocumentation.responseFields(),
                                AutoDocumentation.pathParameters(),
                                AutoDocumentation.requestParameters(),
                                AutoDocumentation.description(),
                                AutoDocumentation.methodAndPath(),
                                AutoDocumentation.section()))
                .build();
    }

Sample application

This project includes a sample application that demonstrates most features:

The generated documentation of the example project can be viewed here.

Snippets

Method and path snippet

Method and path snippet takes a value (path) and method from RequestMapping annotation on the Controller’s method. The shorthand annotations GetMapping, PostMapping, etc. are supported as well.

Code
@RequestMapping(value = "/items", method = RequestMethod.GET)
public ItemResponse getItems() { ... }
Result
GET /items

Description snippet

Description snippet outputs the Controller’s method’s Javadoc. Currently only basic <br> and <p> tags are supported.

Code
/**
 * Returns list of all items.
 * <p>
 * Use query parameters to filter the list by:<br>
 * - orderNumber<br>
 * - type
 */
@RequestMapping...
public ItemResponse getItems() { ... }
Result
Returns list of all items.

Use query parameters to filter the list by:
- orderNumber
- type

Authorization snippet

Authorization snippet does not help in adding authorization to your tests, but it makes it easy to document your authorization information. Usually, a custom RequestPostProcessor is used to add authorization and this is also the place where you add the documentation.

Code
protected RequestPostProcessor userToken() {
    return new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            // <Code to get access token>
            request.addHeader("Authorization", "Bearer " + accessToken);
            return documentAuthorization(request, "User access token required.");
        }
    };
}

@Test
public void addItem() throws Exception {
    mockMvc.perform(post("/items").with(userToken())
    ...
}
Result
User access token required.

Path parameters snippet

Path parameters snippet automatically lists parameters in the path including their type, whether they are optional, and their Javadoc with constraints.

Code
/**
 * @param id ID of the item.
 */
@RequestMapping("{id}")
public ItemResponse getItem(@PathVariable String id) { ... }
Result
Parameter Type Optional Description

id

String

false

ID of the item.

Request parameters snippet

Request parameters snippet automatically lists query parameters including their type, whether they are optional, and their Javadoc with constraints.

Code
/**
 * @param descMatch Lookup on description field.
 */
@RequestMapping("search")
public Page<ItemResponse> searchItem(@RequestParam("desc") String descMatch) { ... }
Result
Parameter Type Optional Description

desc

String

false

Lookup on description field.

Request fields snippet

Request fields snippet automatically lists all fields of the request class, determined from @RequestBody parameter of Controller’s method. List includes name, type, whether the field is optional, and its Javadoc with constraints.

Code
@RequestMapping(method = POST)
public void addItem(@RequestBody ItemUpdateRequest request) { ... }

static class ItemUpdateRequest {
    /**
     * Item ID.
     */
    @NotBlank
    private Integer id;

    /**
     * Item description.
     */
    @Size(max = 20)
    private String description;
}
Result
Path Type Optional Description

id

Integer

false

Item ID.

description

String

true

Item description.
Size must be between 0 and 20 inclusive.

Response fields snippet

Response fields snippet automatically lists all fields of the response class, determined from return value of Controller’s method. List includes name, type, whether the field is optional, and its Javadoc with constraints.

Code
@RequestMapping("{id}")
public ItemResponse getItem(@PathVariable String id) { ... }

static class ItemResponse {
    /**
     * Unique item ID.
     */
    @NotBlank
    private String id;

    /**
     * Item's order number.
     */
    @Min(1)
    private Integer orderNumber;
}
Result
Path Type Optional Description

id

Integer

false

Unique item ID.

orderNumber

Integer

true

Item’s order number.
Must be at least 1.

Section snippet

The section snippet combines most common snippets into one convenient file. It helps you being even more lazy, because a single line of AsciiDoc is sufficient to document one endpoint. Assuming of course that you already documented your code with Javadoc.

Asciidoc
include::{snippets}/your-endpoint/section.adoc[]
Template itself
[[resources-{{link}}]]
=== {{title}}

include::{snippets}/{{path}}/method-path.adoc[]

include::{snippets}/{{path}}/description.adoc[]
{{#sections}}

==== {{header}}

include::{snippets}/{{path}}/{{fileName}}.adoc[]
{{/sections}}
Template placeholders
Placeholder Example Description

{link}

items-id

Derived from the documentation string. For example: items/id.

{title}

Get Item By Id

Derived from method name. For example: getItemById.

{snippets}

target/generated-snippets

Base snippet folder configured via org.springframework.restdocs.outputDir system property.

{sections}

<iterable>

Contains all sections (Snippet classes) which are configured to be included in the section snippet. Moustache templater iterates over this and expands into separate header and link lines.

{header}

Authorization

Snippet header hard-coded inside Snippet class.

{fileName}

authorization

Snippet name hard-coded inside Snippet class.

Snippet configuration

By default, these snippets in the following order are linked inside section snippet:

Snippet name

Example

Description

method-path

POST /items/{id}

Method and path snippet is used to determine the HTTP method and path (non-configurable).

description

Get item by ID.

Description snippet reads the Javadoc on the method (non-configurable).

authorization

Resource is public.

Authorization snippet can be used for custom authorization information, e.g. the OAuth role required for the endpoint

path-parameters

<table>

Path parameters snippet automatically lists parameters in the path including their type, whether they are optional, and their Javadoc.

request-parameters

<table>

Request parameters snippet automatically lists query parameters including their type, whether they are optional, and their Javadoc.

request-fields

<table>

Request fields snippet automatically lists all fields of the request body including their type, whether they are optional, and their Javadoc.

response-fields

<table>

Response fields snippet automatically lists all fields of the response body including their type, whether they are optional, and their Javadoc.

curl-request

$ curl 'http://localhost/items/1'

The original cURL request snippet from Spring REST Docs is used here to provide a cURL example of the request.

http-response

HTTP/1.1 200 OK
{ id: "myId", …​ }

The original HTTP response snippet from Spring REST Docs is used here to provide the HTTP response from the test.

Customisation options

If you want to configure custom sections and their order, instead of using AutoDocumentation.section() you can use AutoDocumentation.sectionBuilder() in the following manner:

Configuring section snippet
AutoDocumentation.sectionBuilder()
    .snippetNames( (1)
        SnippetRegistry.PATH_PARAMETERS,
        SnippetRegistry.REQUEST_PARAMETERS,
        SnippetRegistry.REQUEST_FIELDS,
        SnippetRegistry.RESPONSE_FIELDS)
    .skipEmpty(true) (2)
    .build()
1 only selected snippets will be used in the specified order
2 whether snippets having no content should be skipped from section

Custom snippet

Custom snippets are created by subclassing the Snippet interface (or more conveniently TemplatedSnippet). If you want your custom snippet to be part of section snippet with automatically recognised header and include link (file name), you have to implement SectionSupport interface. Then, inside MockMvc configuration, add this snippet to the list of snippets using documentation configuration’s builder withDefaults(…​) and include snippet name among SectionBuilder’s snippetNames(…​).

Documenting constraints

Spring REST Docs has support for documenting constraints, but using them requires a bit of work and you have to decide on how to include them. Here we are going a step further and automatically use constraints and the constraint groups to derive whether a field is optional and to add the constraint descriptions to the field descriptions.

Constraint that determine optionality

The following three constraints are used to determine whether a field is optional. Their descriptions are not included in the field descriptions.

  • NotNull (Bean Validation)

  • NotEmpty (Hibernate Validator)

  • NotBlank (Hibernate Validator)

Custom constraints

Descriptions for custom constraints can be provided in org.springframework.restdocs.constraints.ConstraintDescriptions.properties. If this file can be loaded as a resource, descriptions are extracted. Each description is a property where the key is the full class name of the annotation suffixed with .description. The value is the description and can contain placeholders for annotation methods, e.g. ${value} to get the content of value(). For more details, see original documentation of here.

Example for the constraint annotation myproject.constraints.OneOf:

ConstraintDescriptions.properties
myproject.constraints.OneOf.description=Must be one of ${value}

Enums

Constraint message for enum is appended to description field as list of allowed values, e.g. Must be one of [A, B]. If you want to customize this message, use above mentioned ConstraintDescriptions.properties approach. The list of values is represented by the ${value} placeholder.

Readable constraint values

Spring REST Docs just calls toString() objects extracted from the constraint for documentation. Here we do a bit more on the following two types:

  • Array: The elements are extracted and toString() is called on each element. The resulting strings are wrapped in brackets and separated by commas, i.e. [element1.toString(), element2.toString(), …​].

  • Class: An instance with the default constructor is created and toString() is called on the instance.

Constraint groups

Constraint groups are also included in the documentation if a description is provided for them. The descriptions are provided in the same way as for custom constraints.

Examples

Descriptions for the examples
ConstraintDescriptions.properties
myproject.constraints.groups.German.description=DE: ${value}
myproject.constraints.groups.English.description=EN: ${value}
myproject.constraints.OneOf.description=Must be one of ${value}
Optionality description
Code
@NotNull(groups = English.class)
private String field;
Result
Parameter Type Optional Description

field

String

true
EN: false

Field description example
Code
@OneOf(value = {"big", "small"}, groups = English.class)
private String field;
Result
Parameter Type Optional Description

field

String

true

EN: Must be one of [big, small]

Contributing

Building from source

Get spring-restdocs-core test JAR

The test JAR is not published, but this project relies on it. If you want to build this project yourself, you first have to build and copy the test JAR on your system.

We use version 1.2.1.RELEASE of Spring REST Docs in this example.

You find the currently required version in pom.xml:

Maven
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-core</artifactId>
    <version>1.2.1.RELEASE</version>
    <classifier>test</classifier>
    <scope>test</scope>
</dependency>

Clone and build a specific version of Spring REST Docs:

Bash
git clone git@github.com:spring-projects/spring-restdocs.git
cd spring-restdocs
git fetch --tags
git checkout tags/v1.2.1.RELEASE
./gradlew build

Afterwards you copy spring-restdocs/spring-restdocs-core/build/libs/spring-restdocs-core-1.2.1.RELEASE-test.jar to ~/.m2/repository/org/springframework/restdocs/spring-restdocs-core/1.2.1.RELEASE so that the test JAR is available to Maven.

Build

Bash (in root folder)
mvn install