本指南指导您完成在Spring应用程序中为HTTP端点生成文档的过程。您将构建一个简单的Spring应用程序,其中一些HTTP端点公开了一个API。您将只使用JUnit和Spring的MockMVC测试Web层,然后使用相同的测试,用Spring REST Docs生成API文档。
目录结构如下

└── src
    └── main
        └── java
            └── hello
                └── app

pom.xml

<?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>

    <groupId>org.springframework</groupId>
    <artifactId>gs-testing-restdocs</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <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>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot将会你做如下的事:

  • 将 classpath 里面所有用到的jar包构建成一个可执行的 JAR 文件,方便执行你的程序
  • 搜索public static void main()方法并且将它当作可执行类
  • 根据springboot版本,去查找相应的依赖类版本,当然你可以定义其它版本。

创建一个简单的应用程序
创建一个控制器
src/main/java/hello/HomeController.java

package hello;

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");
    }

}

创建Application
src/main/java/hello/Application.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

运行并测试程序
以Spring boot app方式运行Application,然后在浏览器打开 http://localhost:8080
但是为了让您自己更确信应用程序在您进行更改时能够正常工作,您需要自动化测试。您还希望公开HTTP端点的文档,并且可以使用 Spring REST Docs生成该端点的动态部分,作为测试的一部分。
您可以做的第一件事是编写一个简单的健全性测试,如果应用程序上下文无法启动,该测试将失败。首先,在测试范围中将 Spring Test 和Spring REST Docs作为依赖项添加到项目中。如果您使用Maven:
pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <scope>test</scope>
        </dependency>

如果使用Gradle:

build.gradle

    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.restdocs:spring-restdocs-mockmvc")

然后使用@RunWith和@SpringBootTest注释以及空测试方法创建一个测试用例:
src/test/java/hello/ApplicationTest.java

package hello;

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {

    @Test
    public void contextLoads() throws Exception {
    }
}

您可以在您的IDE或命令行(mvn test或gradle test)上运行此测试,结果应该通过。
像这样进行健全性检查是很好的,但是您还应该编写一些测试来断言应用程序的行为。一种有用的方法是只测试MVC层,Spring处理传入的HTTP请求并将其传递给控制器。要做到这一点,您可以使用Spring的MockMvc,并通过在测试用例上使用@WebMvcTest注释注入:
src/test/java/hello/WebLayerTest.java

@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")));
    }
}

为文档生成代码段
上面的测试发出(模拟)HTTP请求并断言响应结果。您创建的HTTP API具有动态内容(至少在原则上是这样),因此能够监视测试和了解HTTP请求以便在文档中使用。 Spring REST Docs 允许您通过生成“片段”来实现这一点。只需在测试中添加一个注释和一个额外的“断言”,您就可以很容易地实现这一点。以下是完整的测试:
src/test/java/hello/WebLayerTest.java

package hello;

import org.junit.Test;
import org.junit.runner.RunWith;

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.context.junit4.SpringRunner;
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;

@RunWith(SpringRunner.class)
@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"));
    }
}

新的注释是@AutoConfigureRestDocs(来自Spring boot),它将生成的代码段的目录位置作为参数。新的断言是MockMvcRestDocumentation.document,,它将代码段的字符串标识符作为参数。

Gradle用户可能更喜欢使用build而不是target作为输出目录,但实际上并不重要。这取决于你的选择。

运行此测试,然后查看target/snippets。您应该找到一个名为home(标识符)的目录,其中包含Asciidoctor代码段:

└── target
    └── snippets
        └── home
            └── httpie-request.adoc
            └── curl-request.adoc
            └── http-request.adoc
            └── http-response.adoc

默认代码段采用Asciidoctor 格式,用于HTTP请求和响应,另外两行是为curl和httpie(两个流行的命令行HTTP客户端)的命令行请求示例。

您可以通过向测试中的 document() 断言添加参数来创建其他代码段。例如,您可以使用PayloadDocumentation.responseFields()片段在JSON响应中记录每个字段:
src/test/java/hello/WebLayerTest.java

this.mockMvc.perform(get("/"))
    ...
    .andDo(document("home", responseFields(
        fieldWithPath("message").description("The welcome message for the user.")
    ));

如果您尝试这样做并执行测试,您应该找到一个名为“response fields.adoc”的附加代码段文件,其中包含一个字段名称和描述表。如果您省略了一个字段或将其名称弄错,测试将失败,这是 REST Docs的强大功能。

您可以创建自定义代码段,还可以更改代码段的格式并自定义如增加主机名等内容。有关更多详细信息,请查看Spring REST Docs 。

使用代码片段
要使用生成的代码片段,您需要在项目中包含一些Asciidoctor 内容,然后在构建时包含这些代码片段。要查看此是否生效,请创建一个新文件src/main/asciidoc/index.adoc,并根据需要包含代码段。例如
src/main/asciidoc/index.adoc

= 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.

它的主要功能是使用Asciidoctor include指令(冒号和尾括号告诉解析器在这些行上做一些特殊的事情)包含2个片段。请注意,所包含代码段的路径表示为占位符-Asciidoctor中的“属性”-称为代码段。在这个简单的例子中,唯一的其他标记是顶部的“=”,这是一个级别1的节标题,以及代码段标题(“request”和“response”)前面的“.”。

然后在构建配置中,您需要将这个源文件处理成您选择的文档格式。例如,使用maven生成HTML(执行mvnw包时生成target/generated-docs):
pom.xml

<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>

或者,如果使用Gradle(进行gradlew asciidoctor时生成build/asciidoc)
build.gradle

buildscript {
    repositories {
        ...
        jcenter()
    }
    ...
    dependencies {
        ...
        classpath("org.asciidoctor:asciidoctor-gradle-plugin:1.5.3")
    }
}

...
apply plugin: 'org.asciidoctor.convert'

asciidoctor {
    sourceDir 'src/main/asciidoc'
    attributes \
      'snippets': file('target/snippets')
}

Asciidoctor Gradle插件不在Maven Central中,因此还必须将jcenter() 添加到Gradle中的buildscipt依赖项中。

Gradle中的asciidoctor源的默认位置是src/doc/asciidoc。我们只需要设置sourceDir,因为我们更改了位置以匹配maven的默认值。

祝贺你!您刚刚开发了一个Spring应用程序,并使用Spring Rest Docs对其进行了记录。您可以将创建的HTML文档发布到静态网站,或者打包并从应用程序本身提供服务。您的文档将始终是最新的,如果不是,测试将使构建失败。