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-webtestclient和spring-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 文件夹中:
生成的输出将包含有关服务的信息、如何调用 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 的文档:
在该文档中,如果我们希望引用链接片段,我们可以使用占位符 {snippets} 来包含它,该占位符在 Maven 处理文档时将被替换:
==== Links
include::{snippets}/index-example/links.adoc[]
9.Asciidocs Maven 插件
要将 API 指南从 Asciidoc 转换为可读格式,我们可以向构建生命周期添加 Maven 插件。有几个步骤可以实现此目的:
- 将 Asciidoctor 插件应用到 pom.xml
- 在 testCompile 配置中添加对 spring-restdocs-mockmvc 的依赖,如依赖项部分中所述
- 配置属性以定义生成的片段的输出位置
- 配置 测试 任务以将代码片段目录添加为输出
- 配置 asciidoctor 任务
- 定义一个名为 snippets 的属性,在文档中包含生成的片段时可以使用该属性
- 使任务依赖于 测试 任务,以便在创建文档之前运行测试
- 将 片段 目录配置为输入。所有生成的片段都将在此目录下创建
将代码片段目录添加为 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 输出。
生成的 HTML 文件已格式化且可读,因此 REST 文档可供使用。每次 Maven 构建运行时,也会生成包含最新更新的文档。
11. 结论
没有文档总比错误的文档好,但是 Spring REST 文档将有助于为 RESTful 服务生成准确的文档。
作为一个官方的 Spring 项目,它通过使用三个测试库来实现其目标:Spring MVC Test、 WebTestClient 和 REST Assured。这种生成文档的方法可以帮助支持测试驱动的方法来开发和记录 RESTful API。
您可以在链接的 GitHub 存储库中找到基于本文中的代码的示例项目。