Introduction to Spring REST Docs
1.概述
Spring REST Docs为准确的RESTful服务生成文档。 它结合了手写文档和由Spring测试生成的自动生成的文档片段。
2.优势
该项目背后的主要哲学是使用测试来生成文档。 这样可以确保始终生成的文档与API的实际行为准确匹配。 此外,输出准备好由Asciidoctor处理,Ascidoctor是一个以AsciiDoc语法为中心的发布工具链。 这是用于生成Spring Framework文档的工具。
这些方法减少了其他框架带来的限制。 Spring REST Docs生成的文档准确,简洁且结构合理。 然后,该文档使Web服务使用者可以毫不费力地获取所需的信息。
该工具还有其他一些优点,例如:
生成curl和http请求片段
易于将文档打包到项目jar文件中
轻松向片段添加额外信息
同时支持JSON和XML
可以使用Spring MVC测试支持,Spring Webflux的WebTestClient或REST-Assured编写生成代码片段的测试。
在我们的示例中,我们将使用Spring MVC测试,但是使用其他框架非常相似。
3.依存关系
在项目中开始使用Spring REST Docs的理想方法是使用依赖项管理系统。 在这里,我们使用Maven作为构建工具,因此可以将以下依赖项复制并粘贴到您的POM中:
1 2 3 4 5 | <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 Test框架向要记录的REST服务发出请求。 运行测试将为请求和结果响应生成文档摘要。
我们可以将库与JUnit 4和JUnit 5测试一起使用。 让我们看看每个配置所必需的。
4.1。 JUnit 4配置
生成用于JUnit 4测试的文档摘要的第一步是声明一个公共JUnitRestDocumentation字段,该字段被注释为JUnit @Rule。
JUnitRestDocumentation规则配置了输出目录,应将生成的代码段保存到该目录中。 例如,此目录可以是Maven的构建目录:
1 2 | @Rule public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets"); |
接下来,我们设置MockMvc上下文,以便将其配置为生成文档:
1 2 3 4 5 6 7 8 9 10 11 | @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setUp(){ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); } |
使用MockMvcRestDocumentationConfigurer配置MockMvc对象。 此类的实例可以从org.springframework.restdocs.mockmvc.MockMvcRestDocumentation上的static documentationConfiguration()方法获得。
4.2。 JUnit 5配置
要使用JUnit 5测试,我们必须使用RestDocumentationExtension类扩展测试:
1 2 3 | @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest public class ApiDocumentationJUnit5IntegrationTest { //... } |
使用Maven或/ build / generate-snippets用于Gradle时,此类会自动使用/ target / generate-snippets输出目录进行配置。
接下来,我们必须在@BeforeEach方法中设置MockMvc实例:
1 2 3 4 5 6 | @BeforeEach public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .apply(documentationConfiguration(restDocumentation)).build(); } |
如果我们不使用JUnit进行测试,则必须使用ManualRestDocumentation类。
5. RESTful服务
让我们创建一个可以记录的CRUD RESTful服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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基本端点的链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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()方法:
1 2 3 4 5 | this.mockMvc = MockMvcBuilders //... .alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()))) .build(); |
此设置可确保对于每个MockMvc调用,都在带有测试方法名称的文件夹中创建默认代码片段。 同样,应用prettyPrint()预处理器以更易于阅读的方式显示代码片段。
让我们继续定制一些电话。
要记录包含链接的索引页,我们可以使用static links()方法:
1 2 3 4 5 6 7 8 9 10 | @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()记录请求有效负载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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请求,该请求接收带有title和body字段的CrudInput模型并发送CREATED状态。 使用fieldWithPath()方法记录每个字段。
要记录请求和路径参数,我们可以使用requestParameters()和pathParameters()方法。 两种方法都使用parameterWithName()方法来描述每个参数:
1 2 3 4 5 6 7 8 | @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 / generation-snippets文件夹中:
生成的输出将包含有关服务,如何调用REST服务(如" curl"调用),来自REST服务的HTTP请求和响应以及服务的链接/端点的信息:
CURL命令
1 2 3 | ---- $ curl 'http://localhost:8080/' -i ---- |
HTTP – REST响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [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包含引用它们。 在我们的例子中,我们在src / docs中创建了一个名为api-guide.adoc的文档:
在该文档中,如果我们希望引用链接片段,则可以使用占位符{snippets}将其包括在内,该占位符将在处理文档时由Maven替换:
1 2 3 | ==== Links include::{snippets}/index-example/links.adoc[] |
9. Asciidocs Maven插件
要将API指南从Asciidoc转换为可读格式,我们可以向构建生命周期添加Maven插件。 有几个步骤可以实现此目的:
将Asciidoctor插件应用于pom.xml
如依赖性部分所述,在testCompile配置中添加对spring-restdocs-mockmvc的依赖性
配置属性以定义生成的代码片段的输出位置
配置测试任务以将摘要目录添加为输出
配置asciidoctor任务
定义一个名为片段的属性,当在文档中包含生成的片段时可以使用该属性
使任务取决于测试任务,以便在创建文档之前运行测试
将摘要目录配置为输入。 所有生成的代码段都将在此目录下创建
将片段目录添加为pom.xml中的属性,以便Asciidoctor插件可以使用此路径在此文件夹下生成片段:
1 2 3 | <properties> <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> </properties> |
pom.xml中的Maven插件配置如下所示以从构建生成Asciidoc代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <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目录下的代码段文件夹中生成。 生成代码段后,构建过程将生成HTML输出。
生成的HTML文件已格式化且可读,因此REST文档可以使用。 每次运行Maven构建时,也会使用最新更新生成文档。
11.结论
没有文档总比错误的文档要好,但是Spring REST文档将有助于为RESTful服务生成准确的文档。
作为一个正式的Spring项目,它通过使用三个测试库来实现其目标:Spring MVC Test,WebTestClient和REST Assured。 这种生成文档的方法可以帮助支持测试驱动的方法来开发和文档RESTful API。
您可以在链接的GitHub存储库中基于本文中的代码找到示例项目。