This guide walks you through the process of generating documentation for the HTTP endpoints in a Spring application.
What You Will Build
You will build a simple Spring application with some HTTP endpoints that expose an API. You will test only the web layer by using JUnit and Spring’s MockMvc
. Then you will use the same tests to generate documentation for the API by using Spring REST Docs.
What You Need
-
About 15 minutes
-
A favorite text editor or IDE
-
JDK 1.8 or later
-
You can also import the code straight into your IDE:
How to complete this guide
Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.
To start from scratch, move on to Starting with Spring Initializr.
To skip the basics, do the following:
-
Download and unzip the source repository for this guide, or clone it using Git:
git clone https://github.com/spring-guides/gs-testing-restdocs.git
-
cd into
gs-testing-restdocs/initial
-
Jump ahead to Create a Simple Application.
When you finish, you can check your results against the code in gs-testing-restdocs/complete
.
Starting with Spring Initializr
You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
-
Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.
-
Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
-
Click Dependencies and select Spring Web.
-
Click Generate.
-
Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
If your IDE has the Spring Initializr integration, you can complete this process from your IDE. |
You can also fork the project from Github and open it in your IDE or other editor. |
Create a Simple Application
Create a new controller for your Spring application. The following listing (from src/main/java/com/example/testingrestdocs/HomeController.java
) shows how to do so:
package com.example.testingrestdocs;
import java.util.Collections;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public Map<String, Object> greeting() {
return Collections.singletonMap("message", "Hello, World");
}
}
Run the Application
The Spring Initializr creates a main
class that you can use to launch the application. The following listing (from src/main/java/com/example/testingrestdocs/TestingRestdocsApplication.java
) shows the application class that the Spring Initializr created:
package com.example.testingrestdocs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestingRestdocsApplication {
public static void main(String[] args) {
SpringApplication.run(TestingRestdocsApplication.class, args);
}
}
@SpringBootApplication
is a convenience annotation that adds all of the following:
-
@Configuration
: Tags the class as a source of bean definitions for the application context. -
@EnableAutoConfiguration
: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings. -
@EnableWebMvc
: Flags the application as a web application and activates key behaviors, such as setting up aDispatcherServlet
. Spring Boot adds it automatically when it seesspring-webmvc
on the classpath. -
@ComponentScan
: Tells Spring to look for other components, configurations, and services in the thecom.example.testingrestdocs
package, letting it find theHelloController
class.
The main()
method uses Spring Boot’s SpringApplication.run()
method to launch an application. Did you notice that there is not a single line of XML? There is no web.xml
file, either. This web application is 100% pure Java and you did not have to deal with configuring any plumbing or infrastructure. Spring Boot handles all of that for you.
Logging output is displayed. The service should be up and running within a few seconds.
Test the Application
Now that the application is running, you can test it. You can load the home page at http://localhost:8080
. However, to give yourself more confidence that the application works when you make changes, you want to automate the testing. You also want to publish documentation for the HTTP endpoint. You can generate the dynamic parts of that testing as part of the tests by using Spring REST Docs.
The first thing you can do is write a simple sanity check test that fails if the application context cannot start. To do so, add Spring Test and Spring REST Docs as dependencies to your project, in the test scope. The following listing shows what to add if you use Maven:
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
The following listing shows the completed pom.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>testing-restdocs-complete</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>testing-restdocs-complete</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- tag::test[] -->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<!-- end::test[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- tag::asciidoc[] -->
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html</backend>
<attributes>
<snippets>${project.build.directory}/snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
<!-- end::asciidoc[] -->
</plugins>
</build>
</project>
The following example shows what to add if you use Gradle:
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
The following listing shows the completed build.gradle
file:
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
asciidoctor {
sourceDir 'src/main/asciidoc'
attributes \
'snippets': file('target/snippets')
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// tag::test[]
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
// end::test[]
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
test {
useJUnitPlatform()
}
You can ignore the comments in the build files. They are there to let us pick up parts of the files for inclusion in this guide. |
You have included the mockmvc flavor of REST Docs, which uses Spring MockMvc to capture the HTTP content. If your own application does not use Spring MVC, you can also use a restassured flavor, which works with full stack integration tests. |
Now create a test case with the @RunWith
and @SpringBootTest
annotations and an empty test method, as the following example (from src/test/java/com/example/testingrestdocs/TestingRestdocsApplicationTests.java
) shows:
package com.example.testingrestdocs;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class TestingRestdocsApplicationTests {
@Test
public void contextLoads() throws Exception {
}
}
You can run this test in your IDE or on the command line (by running ./mvnw test
or ./gradlew test
).
It is nice to have a sanity check, but you should also write some tests that assert the behavior of our application. A useful approach is to test only the MVC layer, where Spring handles the incoming HTTP request and hands it off to your controller. To do that, you can use Spring’s MockMvc
and ask for that to be injected for you by using the @WebMvcTest
annotation on the test case. The following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java
) shows how to do so:
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class WebLayerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")));
}
}
Generate Snippets for Documentation
The test in the preceding section makes (mock) HTTP requests and asserts the responses. The HTTP API that you have created has dynamic content (at least in principle), so it would be really nice to be able to spy on the tests and siphon off the HTTP requests for use in the documentation. Spring REST Docs lets you do so by generating “snippets”. You can get this working by adding an annotation to your test and an extra “assertion”. The following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java
) shows the complete test:
package com.example.testingrestdocs;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(HomeController.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class WebLayerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")))
.andDo(document("home"));
}
}
The new annotation is @AutoConfigureRestDocs
(from Spring Boot), which takes an argument for a directory location for the generated snippets. And the new assertion is MockMvcRestDocumentation.document
, which takes an argument for a string identifier for the snippets.
Gradle users might prefer to use build instead of target as an output directory. However, it does not matter. Use whichever you prefer. |
Run the test and then look in target/snippets
. You should find a directory called home
(the identifier) that contains Asciidoctor snippets, as follows:
└── target
└── snippets
└── home
└── curl-request.adoc
└── http-request.adoc
└── http-response.adoc
└── httpie-request.adoc
└── request-body.adoc
└── response-body.adoc
The default snippets are in Asciidoctor format for the HTTP request and response. There are also command line examples for curl
and httpie
(two common and popular command line HTTP clients).
You can create additional snippets by adding arguments to the document()
assertion in the test. For example, you can document each of the fields in a JSON response by using the PayloadDocumentation.responseFields()
snippet, as the following example (from src/test/java/com/example/testingrestdocs/WebLayerTest.java
) shows:
this.mockMvc.perform(get("/"))
...
.andDo(document("home", responseFields(
fieldWithPath("message").description("The welcome message for the user.")
));
If you run the test, you should find an additional snippet file called response-fields.adoc
. It contains a table of field names and descriptions. If you omit a field or get its name wrong, the test fails. This is the power of REST Docs.
You can create custom snippets and change the format of the snippets and customize values, such as the hostname. See the documentation for Spring REST Docs for more detail. |
Using the Snippets
To use the generated snippets, you want to have some Asciidoctor content in the project and then include the snippets at build time. To see this work, create a new file called src/main/asciidoc/index.adoc
and include the snippets as desired. The following example (from src/main/asciidoc/index.adoc
) shows how to do so:
= Getting Started With Spring REST Docs
This is an example output for a service running at http://localhost:8080:
.request
include::{snippets}/home/http-request.adoc[]
.response
include::{snippets}/home/http-response.adoc[]
As you can see the format is very simple, and in fact you always get the same message.
The main feature of this Asciidoc file is that it includes two of the snippets, by using the Asciidoctor include
directive (the colons and the trailing brackets tell the parser to do something special on those lines). Notice that the path to the included snippets is expressed as a placeholder (an attribute
in Asciidoctor) called {snippets}
. The only other markup in this simple case is the =
at the top (which is a level-1 section heading) and the .
before the captions (“request” and “response”) on the snippets. The .
turns the text on that line into a caption.
Then, in the build configuration, you need to process this source file into your chosen documentation format. For example, you can use Maven to generate HTML (target/generated-docs
is generated when you do ./mvnw package
). The following listing shows the Asciidoc portion of the pom.xml
file:
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html</backend>
<attributes>
<snippets>${project.build.directory}/snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
If you use Gradle, build/asciidoc
is generated when you run ./gradlew asciidoctor
. The following listing shows the Asciidoctor-related parts of the build.gradle
file:
plugins {
...
id 'org.asciidoctor.convert' version '1.5.6'
}
...
asciidoctor {
sourceDir 'src/main/asciidoc'
attributes \
'snippets': file('target/snippets')
}
The default location for Asciidoctor sources in Gradle is src/doc/asciidoc . We set the sourceDir to match the default for Maven. |
Summary
Congratulations! You have just developed a Spring application and documented it by using Spring Restdocs. You could publish the HTML documentation you created to a static website or package it up and serve it from the application itself. Your documentation will always be up to date, and tests will fail your build if it is not.
See Also
The following guides may also be helpful:
Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.
All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing. |