SpringRestDocs

通过将手写文档与Spring MVC Test生成的自动生成的片段结合起来,记录RESTful服务。

详细说就是,通过编写单元测试利用asciidoctor生成aodc文档,然后将这些adoc片段输出html


  • pom
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<!--restdoc-->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<!-- 这个插件配置主要是将generated-docs生成的在HTML接口文档
复制到项目的静态文件夹中,以便将项目打包成jar包后,能够访问生成的接口文档(没加)-->
<!-- <execution>
<id>copy-doc-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.outputDirectory}/static/docs
</outputDirectory>
<resources>
<resource>
<directory>
${project.build.directory}/generated-docs
</directory>
</resource>
</resources>
</configuration>
</execution>-->
<!-- spring-restdocs used -->
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html</backend>
<doctype>book</doctype>
<attributes>
<snippets>${project.build.directory}/generated-snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>

示例

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Test
@RunWith(SpringRunner.class)
@SpringBootTest
/**
* @AutoConfigureMockMvc 用于自动配置MockMvc
*/
@AutoConfigureMockMvc
public class BusinessV2Test {

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

public MockMvc mockMvc;

@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation).uris().withScheme("https/http")
.withHost("xxx/xxx").withPort(port))
.build();
}


@Test
public void xxx() {
try {
String example = "xxx";
MockHttpServletRequestBuilder builder = RestDocumentationRequestBuilders
.post("接口").contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE).content(example);

ResultActions perform = this.mockMvc.perform(builder);

perform.andExpect(MockMvcResultMatchers.status().isOk()).
andDo(MockMvcRestDocumentation.document("{ClassName}/{methodName}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
Preprocessors.preprocessResponse(Preprocessors.prettyPrint()),
RequestDocumentation.requestParameters(
RequestDocumentation.parameterWithName("xxx").description("xxx"),
......
PayloadDocumentation.requestFields(
PayloadDocumentation.fieldWithPath("xxx").description("xxx").optional(),
......
PayloadDocumentation.responseFields(
PayloadDocumentation.fieldWithPath("xxx").description("xxx"),
...
......
1
2
3
4
@RunWith(Suite.class)
@Suite.SuiteClasses({xxx.class,xxx.class...})
public class ApplicationTests {
}
  • index.adoc
    固定写法
    在src/main下加asciidoc/index.adoc
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
26
27
28
29
30
31
= -- 文件标题
:author: xxx
:revnumber: xxx
:revdate: 2020-04-23
:toc: left -- 文章目录,左浮动显示
:toc-title: -- 目录
:toclevels: 2
:iconsdir: ./icons -- 图标目录
...

== 2. xxx -- 二级标题

[[xxx]]
=== xxx -- 三级标题

operation::index[snippets='curl-request,http-request,http-response']
等同于
[[example_curl_request]]
== Curl request
注:{snippets}是pom那里配置的路径,index是MockMvcRestDocumentation.document配置的路径
include::{snippets}/index/curl-request.adoc[]

[[example_http_request]]
== HTTP request

include::{snippets}/index/http-request.adoc[]

[[example_http_response]]
== HTTP response

include::{snippets}/index/http-response.adoc[]

总结

将adoc片段输出到指定目录

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

预期返回结果(200,500...)

1
perform.andExpect

将adoc片段输出到指定目录下的子目录,例如下面这个就是输出到类名/方法名的目录下

1
perform.andDo(MockMvcRestDocumentation.document("{ClassName}/{methodName}"))

发送,携带一些请求头和请求参数或是请求体

1
2
3
4
5
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.AUTHORIZATION, "xxx");
httpHeaders.add(HttpHeaders.ACCEPT_LANGUAGE, "xxx");
RestDocumentationRequestBuilders.post("xxx").contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(example).headers(httpHeaders)
...

将结果和请求打印

1
Preprocessors.preprocessRequest(Preprocessors.prettyPrint())Preprocessors.preprocessResponse(Preprocessors.prettyPrint())

请求字段及描述,optional是否必须

1
PayloadDocumentation.requestFields(PayloadDocumentation.fieldWithPath("xxx").description("xxx").optional())

响应字段

1
PayloadDocumentation.responseFields(PayloadDocumentation.fieldWithPath("xxx").description("xxx"))

请求头

1
HeaderDocumentation.requestHeaders(HeaderDocumentation.headerWithName("xxx").description("xxx"))

请求参数

1
RequestDocumentation.requestParameters(RequestDocumentation.parameterWithName("xxx").description("xxx"))

捆绑测试(如果编写了多个测试类)

1
@RunWith(Suite.class)@Suite.SuiteClasses({xxx.class,xxx.class...})

针对对象属性的一些书写方式

https://docs.spring.io/spring-restdocs/docs/1.2.6.RELEASE/reference/html5/#documenting-your-api-request-response-payloads-fields
...

mocktio+restdoc测试代码仓库地址