1. 概述

Spring REST Docs为 RESTful 服务生成准确且可读的文档。它将手写文档与通过 Spring 测试生成的自动生成的文档片段相结合。

2、优点

该项目背后的一项主要理念是使用测试来生成文档。这可确保生成的文档始终与 API 的实际行为准确匹配。此外,输出已准备好由Asciidoctor处理,Asciidoctor 是一个以 AsciiDoc 语法为中心的发布工具链。这与用于生成 Spring 框架文档的工具相同。

这些方法减少了其他框架施加的限制。 Spring REST Docs 生成准确、简洁且结构良好的文档。然后,该文档使 Web 服务使用者能够轻松地获取他们所需的信息。

该工具还有其他一些优点,例如:

  • 生成curl和http请求片段
  • 轻松将文档打包到项目 jar 文件中
  • 轻松向片段添加额外信息
  • 同时支持 JSON 和 XML

生成片段的测试可以使用 Spring MVC 测试支持、Spring Webflux 的 WebTestClient 或 REST-Assured 来编写。

在我们的示例中,我们将使用 Spring MVC 测试,但使用其他框架非常相似。

3. 依赖关系

在项目中开始使用 Spring REST Docs 的理想方法是使用依赖管理系统。在这里,我们使用 Maven 作为构建工具,因此可以将以下依赖项复制并粘贴到您的 POM 中:

<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>

您还可以在此处检查 Maven Central 是否有新版本的依赖项。

在我们的示例中,我们需要 spring-restdocs-mockmvc 依赖项,因为我们使用 Spring MVC 测试支持来创建测试。

如果我们想使用 WebTestClient 或 REST Assured 编写测试,我们将需要spring-restdocs-webtestclientspring-restdocs-restassured依赖项。

4. 配置

如前所述,我们将使用 Spring MVC 测试框架向要记录的 REST 服务发出请求。运行测试会生成请求的文档片段和生成的响应。

我们可以将该库用于 JUnit 4 和 JUnit 5 测试。让我们看看每个所需的配置。

4.1. JUnit 4 配置

为 JUnit 4 测试生成文档片段的第一步是 声明一个公共 JUnitRestDocumentation 字段,该字段注释为 JUnit @Rule

JUnitRestDocumentation 规则配置了生成的代码片段应保存到的输出目录。例如,该目录可以是 Maven 的构建目录:

@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");

接下来,我们设置 MockMvc 上下文,以便将其配置为生成文档:

@Autowired
private WebApplicationContext context;

private MockMvc mockMvc;

@Before
public void setUp(){
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
      .apply(documentationConfiguration(this.restDocumentation))
      .build();
}

MockMvc 对象是使用 MockMvc RestDocumentationConfigurer 配置的。此类的实例可以从 org.springframework.restdocs.mockmvc.MockMvcRestDocumentation 上的静态 DocumentationConfiguration() 方法获取。

4.2. JUnit 5 配置

要使用 JUnit 5 测试,我们必须使用 RestDocumentationExtension 类扩展测试:

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
@SpringBootTest
public class ApiDocumentationJUnit5IntegrationTest { //... }

使用 Maven 时,此类会自动配置 /target/ generated-snippets 输出目录,或者使用 Gradle 时 ,会自动配置 /build/generate-snippets

接下来,我们必须在 @BeforeEach 方法中设置 MockMvc 实例:

@BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
  RestDocumentationContextProvider restDocumentation) {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
      .apply(documentationConfiguration(restDocumentation)).build();
}

如果我们不使用 JUnit 进行测试,那么我们必须使用 ManualRestDocumentation 类。

5. 安心服务

让我们创建一个可以记录的 CRUD RESTful 服务:

@RestController
@RequestMapping("/crud")
public class CRUDController {
    
    @GetMapping
    public List<CrudInput> read(@RequestBody CrudInput crudInput) {
        List<CrudInput> returnList = new ArrayList<CrudInput>();
        returnList.add(crudInput);
        return returnList;
    }
    
    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping
    public HttpHeaders save(@RequestBody CrudInput crudInput) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(
          linkTo(CRUDController.class).slash(crudInput.getTitle()).toUri());
        return httpHeaders;
    }
    
    @DeleteMapping("/{id}")
    public void delete(@PathVariable("id") long id) {
        // delete
    }
}

然后,我们还添加一个 IndexController ,它返回一个页面,其中包含指向 CRUDController 基本端点的链接:

@RestController
@RequestMapping("/")
public class IndexController {

    static class CustomRepresentationModel extends RepresentationModel<CustomRepresentationModel> {
        public CustomRepresentationModel(Link initialLink) {
            super(initialLink);
        }
    }

    @GetMapping
    public CustomRepresentationModel index() {
        return new CustomRepresentationModel(linkTo(CRUDController.class).withRel("crud"));
    }
}

6.JUnit 测试

回到测试中,我们可以使用 MockMvc 实例来调用我们的服务并记录请求和响应。

首先, 为了确保每个 MockMvc 调用都会自动记录,无需任何进一步的配置,我们可以使用 alwaysDo() 方法

this.mockMvc = MockMvcBuilders
  //...
  .alwaysDo(document("{method-name}", 
    preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
  .build();

此设置可确保每次 MockMvc 调用时,都会在具有测试方法名称的文件夹中创建默认代码片段。此外,应用 PrettyPrint() 预处理器以更易于阅读的方式显示片段。

让我们继续自定义一些调用。

要记录包含链接的索引页,我们可以使用静态 links() 方法:

@Test
public void indexExample() throws Exception {
    this.mockMvc.perform(get("/")).andExpect(status().isOk())
      .andDo(document("index", 
        links(linkWithRel("crud").description("The CRUD resource")), 
        responseFields(subsectionWithPath("_links")
          .description("Links to other resources"))
        responseHeaders(headerWithName("Content-Type")
          .description("The Content-Type of the payload"))));
}

在这里,我们使用 linkWithRel() 方法来记录指向 /crud 的链接。

为了将 Content-Type 标头添加到响应中,我们使用 headerWithName() 方法对其进行记录,并将其添加到 responseHeaders() 方法中。

我们还使用 responseFields() 方法记录响应负载。 这可用于使用 subsectionWithPath() 或 fieldWithPath() 方法来记录响应的更复杂的子部分或单个字段。

与响应负载类似, 我们也可以使用 requestPayload() 记录请求负载:

@Test
public void crudCreateExample() throws Exception {
    Map<String, Object> crud = new HashMap<>();
    crud.put("title", "Sample Model");
    crud.put("body", "http://www.baeldung.com/");
       
    this.mockMvc.perform(post("/crud").contentType(MediaTypes.HAL_JSON)
      .content(this.objectMapper.writeValueAsString(crud)))
      .andExpect(status().isCreated())
      .andDo(document("create-crud-example", 
        requestFields(fieldWithPath("id").description("The id of the input"),
          fieldWithPath("title").description("The title of the input"),
          fieldWithPath("body").description("The body of the input"),
        ))));
}

在此示例中,我们记录了 POST 请求,该请求接收带有标题和正文字段的 CrudInput 模型并发送 CREATED 状态。 每个字段都使用 fieldWithPath() 方法进行记录。

要记录请求和路径参数,我们可以使用 requestParameters()pathParameters() 方法。 两种方法都使用 parameterWithName() 方法来描述每个参数:

@Test
public void crudDeleteExample() throws Exception {
    this.mockMvc.perform(delete("/crud/{id}", 10)).andExpect(status().isOk())
      .andDo(document("crud-delete-example", 
      pathParameters(
        parameterWithName("id").description("The id of the input to delete")
      )));
}

在这里,我们记录了接收 id 路径参数的删除端点。

Spring REST Docs 项目包含更强大的文档功能,例如可以在文档中找到的字段约束和请求部分。

7. 输出

构建成功运行后,将生成 REST 文档片段的输出并将其保存到 target/ generated-snippets 文件夹中:

屏幕截图 2016-04-04 11.48.52 PM

生成的输出将包含有关服务的信息、如何调用 REST 服务(例如“curl”调用)、来自 REST 服务的 HTTP 请求和响应以及服务的链接/端点:

卷曲命令

----
$ curl 'http://localhost:8080/' -i
----

HTTP – REST 响应

[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Content-Length: 93

{
  "_links" : {
    "crud" : {
      "href" : "http://localhost:8080/crud"
    }
  }
}
----

8. 使用代码片段创建文档

要在较大的文档中使用这些片段,您可以使用 Asciidoc include 引用它们。 在我们的例子中,我们在 src/docs 中创建了一个名为 api-guide.adoc 的文档:

屏幕截图 2016-05-01 8.51.48 PM

在该文档中,如果我们希望引用链接片段,我们可以使用占位符 {snippets} 来包含它,该占位符在 Maven 处理文档时将被替换:

==== Links

include::{snippets}/index-example/links.adoc[]

9.Asciidocs Maven 插件

要将 API 指南从 Asciidoc 转换为可读格式,我们可以向构建生命周期添加 Maven 插件。有几个步骤可以实现此目的:

  1. 将 Asciidoctor 插件应用到 pom.xml
  2. testCompile 配置中添加对 spring-restdocs-mockmvc 的依赖,如依赖项部分中所述
  3. 配置属性以定义生成的片段的输出位置
  4. 配置 测试 任务以将代码片段目录添加为输出
  5. 配置 asciidoctor 任务
  6. 定义一个名为 snippets 的属性,在文档中包含生成的片段时可以使用该属性
  7. 使任务依赖于 测试 任务,以便在创建文档之前运行测试
  8. 片段 目录配置为输入。所有生成的片段都将在此目录下创建

将代码片段目录添加为 pom.xml 中的属性,以便 Asciidoctor 插件可以使用此路径在此文件夹下生成代码片段:

<properties>
    <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
</properties>

pom.xml 中用于从构建生成 Asciidoc 片段的 Maven 插件配置如下:

<plugin> 
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>1.5.6</version>
    <executions>
        <execution>
            <id>generate-docs</id>
            <phase>package</phase> 
            <goals>
                <goal>process-asciidoc</goal>
            </goals>
            <configuration>
                <backend>html</backend>
                <doctype>book</doctype>
                <attributes>
                    <snippets>${snippetsDirectory}</snippets> 
                </attributes>
                <sourceDirectory>src/docs/asciidocs</sourceDirectory>
                <outputDirectory>target/generated-docs</outputDirectory>
             </configuration>
     </execution>
    </executions>
</plugin>

10. API文档生成流程

当 Maven 构建运行并执行测试时,所有代码片段将在配置的 target/ generated-snippets 目录下的 snippets 文件夹中生成。生成代码片段后,构建过程会生成 HTML 输出。

屏幕截图 2016-05-08 11.32.25 PM

生成的 HTML 文件已格式化且可读,因此 REST 文档可供使用。每次 Maven 构建运行时,也会生成包含最新更新的文档。

11. 结论

没有文档总比错误的文档好,但是 Spring REST 文档将有助于为 RESTful 服务生成准确的文档。

作为一个官方的 Spring 项目,它通过使用三个测试库来实现其目标:Spring MVC Test、 WebTestClient 和 REST Assured。这种生成文档的方法可以帮助支持测试驱动的方法来开发和记录 RESTful API。

您可以在链接的 GitHub 存储库中找到基于本文中的代码的示例项目。