# Spring REST Docs

将手写文档与通过 Spring MVC 测试、WebTestClient 或 Rest Assured 生成的自动生成的片段结合在一起来实现文档服务。

# 导言

Spring REST DOCS 的目的是帮助你为 RESTful 服务生成准确且可读的文档。

编写高质量的文档是困难的。减轻这一困难的一种方法是使用非常适合这项工作的工具。为此, Spring REST DOCS 默认使用ASCIIDoctor (opens new window)。ASCIIDoctor 处理纯文本并生成 HTML,并根据你的需要进行样式和布局。如果你愿意,还可以将 Spring REST DOCS 配置为使用 Markdown。

Spring REST DOCS 使用由用 Spring MVC 的、 Spring WebFlux 的[](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.0.x/ Spring-Framework-Reference/Testing.html#WebTestClient)或编写的测试产生的代码片段。这种测试驱动的方法有助于保证服务文档的准确性。如果代码片段不正确,则生成它的测试失败。

记录一个 RESTful 服务主要是描述它的资源。每个资源描述的两个关键部分是它使用的 HTTP 请求的详细信息和它产生的 HTTP 响应。 Spring REST DOCS 允许你使用这些资源以及 HTTP 请求和响应,从而保护你的文档不受服务实现的内部细节的影响。这种分离可以帮助你记录服务的 API,而不是它的实现。它还可以帮助你改进实现,而无需重新编写文档。

# 开始

本节描述了如何开始使用 Spring REST DOCS。

# 示例应用程序

如果你想直接进入,可以使用一些示例应用程序:

样本 Build system 说明
Spring Data REST (opens new window) Maven 演示如何为使用Spring Data REST (opens new window)实现的服务创建入门指南和 API 指南。
Spring HATEOAS (opens new window) Gradle 演示如何为使用Spring HATEOAS (opens new window)实现的服务创建入门指南和 API 指南。
样本 Build system Description
WebTestClient (opens new window) Gradle Demonstrates the use of Spring REST docs with Spring WebFlux’s WebTestClient.
样本 Build system 说明
放心吧 (opens new window) Gradle Demonstrates the use of Spring REST Docs with 放心吧 (opens new window).
Sample Build system Description
Slate (opens new window) Gradle Demonstrates the use of Spring REST Docs with Markdown andSlate (opens new window).
TestNG (opens new window) Gradle Demonstrates the use of Spring REST Docs with TestNG (opens new window).
JUnit 5 (opens new window) Gradle Demonstrates the use of Spring REST Docs with JUnit 5 (opens new window).

# 所需经费

Spring REST DOCS 具有以下最低要求:

  • Java8

  • Spring 框架 5(5.0.2 或更高版本)

此外,spring-restdocs-restassured模块需要 REST SUARD3.0。

# 构建配置

使用 Spring REST DOCS 的第一步是配置项目的构建。Spring HATEOAS (opens new window)Spring Data REST (opens new window)样本分别包含一个build.gradlepom.xml,你可能希望将其用作参考。配置的关键部分在以下清单中进行了描述:

Maven

<dependency> (1)
	<groupId>org.springframework.restdocs</groupId>
	<artifactId>spring-restdocs-mockmvc</artifactId>
	<version>{project-version}</version>
	<scope>test</scope>
</dependency>

<build>
	<plugins>
		<plugin> (2)
			<groupId>org.asciidoctor</groupId>
			<artifactId>asciidoctor-maven-plugin</artifactId>
			<version>1.5.8</version>
			<executions>
				<execution>
					<id>generate-docs</id>
					<phase>prepare-package</phase> (3)
					<goals>
						<goal>process-asciidoc</goal>
					</goals>
					<configuration>
						<backend>html</backend>
						<doctype>book</doctype>
					</configuration>
				</execution>
			</executions>
			<dependencies>
				<dependency> (4)
					<groupId>org.springframework.restdocs</groupId>
					<artifactId>spring-restdocs-asciidoctor</artifactId>
					<version>{project-version}</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</build>
1 test范围中添加对spring-restdocs-mockmvc的依赖关系。
如果你想使用WebTestClient或 Rest Assured 而不是 MockMVC,请分别在spring-restdocs-webtestclientspring-restdocs-restassured上添加依赖关系。
2 添加 ASCIIDoctor 插件。
3 使用prepare-package允许文档包含在包中
4 添加spring-restdocs-asciidoctor作为 ASCIIDoctor 插件的依赖项。
这将自动配置在snippets文件中使用的snippets属性,以指向target/generated-snippets
它还将允许你使用operation块宏。

Gradle

plugins { (1)
	id "org.asciidoctor.jvm.convert" version "3.3.2"
}

configurations {
	asciidoctorExt (2)
}

dependencies {
	asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' (3)
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' (4)
}

ext { (5)
	snippetsDir = file('build/generated-snippets')
}

test { (6)
	outputs.dir snippetsDir
}

asciidoctor { (7)
	inputs.dir snippetsDir (8)
	configurations 'asciidoctorExt' (9)
	dependsOn test (10)
}
1 应用 ASCIIDoctor 插件。
2 为扩展 ASCIIDoctor 的依赖项声明asciidoctorExt配置。
3 asciidoctorExt配置中添加对spring-restdocs-asciidoctor的依赖关系。
这将自动配置在你的.adoc文件中使用的snippets属性,以指向build/generated-snippets
它还将允许你使用operation块宏。
4 testImplementation配置中添加对spring-restdocs-mockmvc的依赖关系。
如果你想使用WebTestClient或 Rest Assured 而不是 MockMVC,请分别在spring-restdocs-webtestclientspring-restdocs-restassured上添加依赖关系。
5 配置一个属性来定义生成的片段的输出位置。
6 配置test任务,将 snippets 目录添加为输出。
7 配置asciidoctor任务。
8 将 snippets 目录配置为输入。
9 为扩展配置asciidoctorExt配置的使用。
10 使任务依赖于测试任务,以便在创建文档之前运行测试。

# 打包文档

你可能希望将生成的文档打包到项目的 JAR 文件中——例如,在 Spring 启动时将其用作静态内容 (opens new window)。要做到这一点,请配置项目的构建,以便:

  1. 文档是在构建 JAR 之前生成的。

  2. 生成的文档包含在 JAR 中

下面的列表显示了如何在 Maven 和 Gradle 中实现这一点:

Maven

<plugin> (1)
	<groupId>org.asciidoctor</groupId>
	<artifactId>asciidoctor-maven-plugin</artifactId>
	<!-- … -->
</plugin>
<plugin> (2)
	<artifactId>maven-resources-plugin</artifactId>
	<version>2.7</version>
	<executions>
		<execution>
			<id>copy-resources</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>copy-resources</goal>
			</goals>
			<configuration> (3)
				<outputDirectory>
					${project.build.outputDirectory}/static/docs
				</outputDirectory>
				<resources>
					<resource>
						<directory>
							${project.build.directory}/generated-docs
						</directory>
					</resource>
				</resources>
			</configuration>
		</execution>
	</executions>
</plugin>
1 ASCIIDoctor 插件的现有声明。
2 资源插件必须在 ASCIIDoctor 插件之后声明,因为它们绑定到相同的阶段(prepare-package),并且资源插件必须在 ASCIIDoctor 插件之后运行,以确保在复制文档之前生成文档。
3 将生成的文档复制到构建输出的static/docs目录中,然后将其包含在 JAR 文件中。

Gradle

bootJar {
	dependsOn asciidoctor (1)
	from ("${asciidoctor.outputDir}/html5") { (2)
		into 'static/docs'
	}
}
1 确保在构建 JAR 之前已经生成了文档。
2 将生成的文档复制到 JAR 的static/docs目录中。

# 生成文档片段

Spring REST DOCS 使用 Spring MVC 的, Spring WebFlux 的[](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.0.x/ Spring-Framework-Reference/Testing.html#WebTestClient),或向你正在记录的服务发出请求。然后,它为请求和结果响应生成文档片段。

# 设置你的测试

你如何设置测试取决于你所使用的测试框架。 Spring REST DOCS 为 JUnit4 和 JUnit5 提供一流的支持。也支持其他框架,例如 TestNG,尽管需要稍多的设置。

# 设置你的 JUnit4 测试

当使用 JUnit4 时,生成文档片段的第一步是声明一个public``JUnitRestDocumentation字段,该字段被注释为 JUnit@Rule。下面的示例展示了如何做到这一点:

@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();

默认情况下,JUnitRestDocumentation规则会根据项目的构建工具自动配置一个输出目录:

Build tool 输出目录
Maven target/generated-snippets
Gradle build/generated-snippets

你可以通过在创建JUnitRestDocumentation实例时提供一个输出目录来覆盖默认值。下面的示例展示了如何做到这一点:

@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("custom");

接下来,你必须提供一个@Before方法来配置 MockMVC、WebTestClient 或 Rest Assured。下面的例子说明了如何做到这一点:

MockMVC

private MockMvc mockMvc;

@Autowired
private WebApplicationContext context;

@Before
public void setUp() {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
			.apply(documentationConfiguration(this.restDocumentation)) (1)
			.build();
}
1 通过使用MockMVCRestDocumentationConfigurer配置MockMVC实例。
可以从documentationConfiguration()上的静态documentationConfiguration()方法获得该类的实例。

WebTestClient

private WebTestClient webTestClient;

@Autowired
private ApplicationContext context;

@Before
public void setUp() {
	this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
			.filter(documentationConfiguration(this.restDocumentation)) (1)
			.build();
}
1 WebTestClient实例配置为将WebTestclientRestDocumentationConfigurer添加为ExchangeFilterFunction
可以从org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation上的静态documentationConfiguration()方法获得该类的实例。

放心吧

private RequestSpecification spec;

@Before
public void setUp() {
	this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)) (1)
			.build();
}
1 通过将RestAssuredRestDocumentationConfigurer添加为Filter来配置 Rest Assured。
你可以在RestAssuredRestDocumentation包中从RestAssuredRestDocumentation上的静态documentationConfiguration()方法获得该类的实例。

配置器应用合理的默认值,还提供了用于定制配置的 API。有关更多信息,请参见配置部分

# 设置你的 JUnit5 测试

当使用 JUnit5 时,生成文档片段的第一步是将RestDocumentationExtension应用到测试类。下面的示例展示了如何做到这一点:

@ExtendWith(RestDocumentationExtension.class)
public class JUnit5ExampleTests {

在测试典型的 Spring 应用程序时,还应该应用SpringExtension:

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
public class JUnit5ExampleTests {

根据项目的构建工具,RestDocumentationExtension会自动配置一个输出目录:

Build tool 输出目录
Maven target/generated-snippets
Gradle build/generated-snippets

如果你使用的是 JUnit5.1,那么你可以通过在你的测试类中将扩展注册为字段并在创建它时提供一个输出目录来覆盖默认的扩展。下面的示例展示了如何做到这一点:

public class JUnit5ExampleTests {

	@RegisterExtension
	final RestDocumentationExtension restDocumentation = new RestDocumentationExtension ("custom");

}

接下来,你必须提供一个@BeforeEach方法来配置 MockMVC、WebTestClient 或 放心吧。下面的列表展示了如何做到这一点:

MockMVC

private MockMvc mockMvc;

@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
			.apply(documentationConfiguration(restDocumentation)) (1)
			.build();
}
1 MockMVC实例是通过使用MockMVCRestDocumentationConfigurer来配置的。
你可以在org.springframework.restdocs.mockmvc.MockMVCRestDocumentation上从静态documentationConfiguration()方法获得该类的实例。

WebTestClient

private WebTestClient webTestClient;

@BeforeEach
public void setUp(ApplicationContext applicationContext, RestDocumentationContextProvider restDocumentation) {
	this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext).configureClient()
			.filter(documentationConfiguration(restDocumentation)) (1)
			.build();
}
1 WebTestClient实例配置为将WebTestClientRestDocumentationConfigurer添加为ExchangeFilterFunction
你可以从org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation上的静态documentationConfiguration()方法获得该类的实例。

放心吧

private RequestSpecification spec;

@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
	this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation)) (1)
			.build();
}
1 通过将RestAssuredRestDocumentationConfigurer添加为Filter来配置 Rest Assured。
你可以在RestAssuredRestDocumentation包中从RestAssuredRestDocumentation上的静态documentationConfiguration()方法获得该类的实例。

配置器应用合理的默认值,还提供了用于定制配置的 API。有关更多信息,请参见配置部分

# 在不使用 JUnit 的情况下设置测试

不使用 JUnit 时的配置在很大程度上与使用 JUnit 时的配置相似。这一节描述了主要的区别。测试样本 (opens new window)也说明了这种方法。

第一个区别是,你应该使用ManualRestDocumentation来代替JUnitRestDocumentation。另外,你不需要@Rule注释。下面的示例展示了如何使用ManualRestDocumentation:

private ManualRestDocumentation restDocumentation = new ManualRestDocumentation();

其次,在每次测试之前,你必须调用ManualRestDocumentation.beforeTest(Class, String)。你可以作为配置 MockMVC、WebTestClient 或 REST ASSURED 的方法的一部分来执行此操作。下面的例子说明了如何做到这一点:

MockMVC

private MockMvc mockMvc;

@Autowired
private WebApplicationContext context;

@BeforeMethod
public void setUp(Method method) {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
			.apply(documentationConfiguration(this.restDocumentation)).build();
	this.restDocumentation.beforeTest(getClass(), method.getName());
}

WebTestClient

private WebTestClient webTestClient;

@Autowired
private ApplicationContext context;

@BeforeMethod
public void setUp(Method method) {
	this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
			.filter(documentationConfiguration(this.restDocumentation)) (1)
			.build();
	this.restDocumentation.beforeTest(getClass(), method.getName());
}

放心吧

private RequestSpecification spec;

@BeforeMethod
public void setUp(Method method) {
	this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation)).build();
	this.restDocumentation.beforeTest(getClass(), method.getName());
}

最后,你必须在每次测试之后调用ManualRestDocumentation.afterTest。下面的示例展示了如何使用 TestNG 来实现这一点:

@AfterMethod
public void tearDown() {
	this.restDocumentation.afterTest();
}

# Invoking the RESTful Service

现在你已经配置了测试框架,你可以使用它来调用 RESTful 服务并记录请求和响应。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) (1)
		.andExpect(status().isOk()) (2)
		.andDo(document("index")); (3)
1 调用服务的根(/)并指示需要一个application/json响应。
2 断言服务产生了预期的响应。
3 记录对服务的调用,将片段写入名为index的目录(位于配置的输出目录下面)。
片段由RestDocumentationResultHandler编写。
你可以从org.springframework.restdocs.mockmvc.MockMVCRestDocumentation上的静态document方法获得该类的实例。

WebTestClient

this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON) (1)
		.exchange().expectStatus().isOk() (2)
		.expectBody().consumeWith(document("index")); (3)
1 调用服务的根(/)并指示需要一个application/json响应。
2 断言服务产生了预期的响应。
3 记录对服务的调用,将片段写入名为index的目录(位于配置的输出目录下面)。
片段由ExchangeResultConsumer所写。
你可以从org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation上的静态document方法获得这样的使用者。

放心吧

RestAssured.given(this.spec) (1)
		.accept("application/json") (2)
		.filter(document("index")) (3)
		.when().get("/") (4)
		.then().assertThat().statusCode(is(200)); (5)
1 应用在@Before方法中初始化的规范。
2 表示需要application/json响应。
3 记录下对该服务的调用,将片段写入名为index的目录(位于配置的输出目录下面)。
片段由RestDocumentationFilter编写。
你可以在org.springframework.restdocs.restassured3包中从RestAssuredRestDocumentation上的静态document方法获得该类的实例。
4 调用服务的根(/)。
5 断言服务产生了预期的响应。

默认情况下,编写了六个片段:

  • <output-directory>/index/curl-request.adoc

  • <output-directory>/index/http-request.adoc

  • <output-directory>/index/http-response.adoc

  • <output-directory>/index/httpie-request.adoc

  • <output-directory>/index/request-body.adoc

  • <output-directory>/index/response-body.adoc

参见记录你的 API,以获取有关这些片段和可由 Spring REST DOCS 产生的其他片段的更多信息。

# 使用片段

在使用生成的代码片段之前,你必须创建一个.adoc源文件。只要该文件有.adoc后缀,你就可以为该文件命名任何你喜欢的名称。生成的 HTML 文件具有相同的名称,但带有.html后缀。源文件和生成的 HTML 文件的默认位置取决于你是使用 Maven 还是 Gradle:

Build tool Source files 生成的文件
Maven src/main/asciidoc/*.adoc target/generated-docs/*.html
Gradle src/docs/asciidoc/*.adoc build/asciidoc/html5/*.html

然后,可以使用包括宏 (opens new window)将生成的片段包括在手动创建的 ASCIIDoc 文件中(在本节前面描述)。可以使用snippets属性,该属性由spring-restdocs-asciidoctor中配置的构建配置自动设置,以引用片段输出目录。下面的示例展示了如何做到这一点:

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

# 记录你的 API

本节提供了有关使用 Spring REST DOCS 来记录 API 的更多详细信息。

# Hypermedia

Spring REST DOCS 提供了对基于超媒体的 (opens new window)API 中的链接进行文档化的支持。以下示例展示了如何使用它:

MockMVC

this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("index", links((1)
				linkWithRel("alpha").description("Link to the alpha resource"), (2)
				linkWithRel("bravo").description("Link to the bravo resource")))); (3)
1 配置 Spring REST DOCS 以生成描述响应链接的片段。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态links方法。
2 期望一个relalpha的链接。
linkWithRel上使用静态linkWithRel方法。
3 期望一个链接的relbravo

WebTestClient

this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
		.expectBody().consumeWith(document("index", links((1)
				linkWithRel("alpha").description("Link to the alpha resource"), (2)
				linkWithRel("bravo").description("Link to the bravo resource")))); (3)
1 配置 Spring REST DOCS 以生成描述响应链接的片段。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态links方法。
2 期望一个其relalpha的链接。
linkWithRel上使用静态linkWithRel方法。
3 期望一个链接的relbravo

放心吧

RestAssured.given(this.spec).accept("application/json").filter(document("index", links((1)
		linkWithRel("alpha").description("Link to the alpha resource"), (2)
		linkWithRel("bravo").description("Link to the bravo resource")))) (3)
		.get("/").then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述响应链接的片段。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态links方法。
2 期望一个relalpha的链接。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态linkWithRel方法。
3 期望一个链接的relbravo

结果是一个名为links.adoc的片段,其中包含一个描述资源链接的表。

如果响应中的链接具有title,则可以从其描述符中省略该描述,并使用title
如果省略该描述且该链接没有title,则会发生故障。

在记录链接时,如果在响应中发现了未记录的链接,则测试将失败。类似地,如果在响应中未找到文档化的链接,并且该链接未标记为可选的,则测试也会失败。

如果不想记录链接,可以将其标记为“忽略”。这样做可以防止它出现在生成的代码片段中,同时避免上述的故障。

你还可以在放松模式中记录链接,在这种模式中,任何未记录的链接都不会导致测试失败。要做到这一点,请在org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用relaxedLinks方法。当你只想关注链接的一个子集时,记录特定的场景时,这可能会很有用。

# 超媒体链接格式

默认情况下可以理解两种链接格式:

  • Atom:链接应该在一个名为links的数组中。当响应的内容类型与application/json兼容时,默认情况下使用此选项。

  • HAL:链接预期在一个名为_links的映射中。当响应的内容类型与application/hal+json兼容时,默认情况下使用此选项。

如果使用 Atom 格式或 HAL 格式的链接但具有不同的内容类型,则可以提供一个内置的LinkExtractor实现到links。下面的例子说明了如何做到这一点:

MockMVC

.andDo(document("index", links(halLinks(), (1)
		linkWithRel("alpha").description("Link to the alpha resource"),
		linkWithRel("bravo").description("Link to the bravo resource"))));
1 指示链接为 HAL 格式。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态halLinks方法。

WebTestClient

.consumeWith(document("index", links(halLinks(), (1)
		linkWithRel("alpha").description("Link to the alpha resource"),
		linkWithRel("bravo").description("Link to the bravo resource"))));
1 指示链接为 HAL 格式。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态halLinks方法。

放心吧

.filter(document("index", links(halLinks(), (1)
		linkWithRel("alpha").description("Link to the alpha resource"),
		linkWithRel("bravo").description("Link to the bravo resource"))))
1 指示链接为 HAL 格式。
org.springframework.restdocs.hypermedia.HypermediaDocumentation上使用静态halLinks方法。

如果你的 API 以 Atom 或 HAL 以外的格式表示其链接,则可以提供你自己的LinkExtractor接口的实现,以从响应中提取链接。

# 忽略公共链接

在使用 HAL 时,你可能希望在概述部分对它们进行一次文档记录,然后在 API 文档的其余部分中忽略它们,而不是对每个响应都通用的链接进行文档记录,例如selfcuries。为此,你可以构建支持重用代码片段,将链接描述符添加到预先配置为忽略某些链接的片段中。下面的示例展示了如何做到这一点:

public static LinksSnippet links(LinkDescriptor... descriptors) {
	return HypermediaDocumentation.links(linkWithRel("self").ignored().optional(), linkWithRel("curies").ignored())
			.and(descriptors);
}

# 请求和响应有效载荷

除了特定于超媒体的支持前面描述的外,还提供了对请求和响应有效负载的一般文档的支持。

默认情况下, Spring REST DOCS 自动生成用于请求的主体和响应的主体的片段。这些片段分别命名为request-body.adocresponse-body.adoc

# 请求和响应字段

为了提供请求或响应有效负载的更详细的文档,提供了对有效负载字段进行文档记录的支持。

考虑以下有效载荷:

{
	"contact": {
		"name": "Jane Doe",
		"email": "[email protected]"
	}
}

你可以将上一个示例的字段记录如下:

MockMVC

this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("index", responseFields((1)
				fieldWithPath("contact.email").description("The user's email address"), (2)
				fieldWithPath("contact.name").description("The user's name")))); (3)
1 配置 Spring REST DOCS 以生成描述响应负载中的字段的片段。
要记录请求,可以使用requestFields
都是org.springframework.restdocs.payload.PayloadDocumentation上的静态方法。
2 期望具有路径contact.email的字段。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态fieldWithPath方法。
3 期望具有路径contact.name的字段。

WebTestClient

this.webTestClient.get().uri("user/5").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("user",
		responseFields((1)
			fieldWithPath("contact.email").description("The user's email address"), (2)
			fieldWithPath("contact.name").description("The user's name")))); (3)
1 配置 Spring REST DOCS 以生成描述响应负载中的字段的片段。
要记录请求,可以使用requestFields
都是org.springframework.restdocs.payload.PayloadDocumentation上的静态方法。
2 期望具有路径contact.email的字段。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态fieldWithPath方法。
3 期望具有路径contact.name的字段。

放心吧

RestAssured.given(this.spec).accept("application/json").filter(document("user", responseFields((1)
		fieldWithPath("contact.name").description("The user's name"), (2)
		fieldWithPath("contact.email").description("The user's email address")))) (3)
		.when().get("/user/5").then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述响应有效负载中的字段的片段。
要记录请求,可以使用requestFields
都是org.springframework.restdocs.payload.PayloadDocumentation上的静态方法。
2 期望具有路径contact.email的字段。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态fieldWithPath方法。
3 期望具有路径contact.name的字段。

结果是一个包含描述字段的表的片段。对于请求,此段名为request-fields.adoc。对于响应,此段名为response-fields.adoc

在记录字段时,如果在有效负载中发现了未记录的字段,则测试将失败。类似地,如果在有效负载中未找到已记录的字段,并且该字段未标记为可选字段,则测试也会失败。

如果你不想提供所有字段的详细文档,那么可以对有效负载的整个小节进行文档记录。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("index", responseFields((1)
				subsectionWithPath("contact").description("The user's contact details")))); (1)
1 用路径contact记录该小节。contact.emailcontact.name现在也被视为已被记录。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态subsectionWithPath方法。

WebTestClient

this.webTestClient.get().uri("user/5").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("user",
		responseFields(
			subsectionWithPath("contact").description("The user's contact details")))); (1)
1 用路径contact记录该小节。contact.emailcontact.name现在也被视为已被记录。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态subsectionWithPath方法。

放心吧

RestAssured.given(this.spec).accept("application/json")
		.filter(document("user",
				responseFields(subsectionWithPath("contact").description("The user's contact details")))) (1)
		.when().get("/user/5").then().assertThat().statusCode(is(200));
1 用路径contact记录该小节。contact.emailcontact.name现在也被视为已被记录。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态subsectionWithPath方法。

subsectionWithPath对于提供有效载荷的特定部分的高级概述很有用。然后,你可以为一个小节生成单独的、更详细的文档。见记录请求或响应有效负载的一个分节

如果你根本不想记录某个字段或小节,可以将其标记为“忽略”。这可以防止它出现在生成的代码片段中,同时避免前面描述的故障。

你还可以在放松模式下记录字段,在这种模式下,任何未记录的字段都不会导致测试失败。为此,在org.springframework.restdocs.payload.PayloadDocumentation上使用relaxedRequestFieldsrelaxedResponseFields方法。在记录一个特定的场景时,如果你希望只关注有效负载的一个子集,这可能会很有用。

默认情况下, Spring REST DOCS 假定你正在记录的负载是 JSON。
如果你想记录 XML 负载,请求或响应的内容类型必须与application/xml兼容。
# JSON 有效载荷中的字段

本节介绍如何使用 JSON 有效负载中的字段。

# JSON 字段路径

JSON 字段路径使用点表示法或括号表示法。点记号使用’.’分隔路径中的每个键(例如,a.b)。括号表示法将每个键包装在方括号和单引号中(例如,['a']['b'])。在这两种情况下,[]都用于标识一个数组。点表示法更简洁,但是使用括号表示法可以在键名中使用.(例如,['a.b'])。这两个不同的符号可以在相同的路径中使用(例如,a['b'])。

考虑以下 JSON 有效负载:

{
	"a":{
		"b":[
			{
				"c":"one"
			},
			{
				"c":"two"
			},
			{
				"d":"three"
			}
		],
		"e.dot" : "four"
	}
}

在前面的 JSON 有效负载中,以下路径都存在:

Path 价值
a 包含b的对象
a.b 包含三个对象的数组
['a']['b'] 包含三个对象的数组
a['b'] 包含三个对象的数组
['a'].b 包含三个对象的数组
a.b[] 包含三个对象的数组
a.b[].c 包含字符串onetwo的数组
a.b[].d 字符串three
a['e.dot'] 字符串four
['a']['e.dot'] 字符串four

你还可以记录在根目录下使用数组的有效负载。路径[]表示整个数组。然后,你可以使用括号或点表示法来标识数组条目中的字段。例如,[].id对应于以下数组中每个对象的id字段:

[
	{
		"id":1
	},
	{
		"id":2
	}
]

可以使用*作为通配符来匹配具有不同名称的字段。例如,users.*.role可用于记录以下 JSON 中每个用户的角色:

{
	"users":{
		"ab12cd34":{
			"role": "Administrator"
		},
		"12ab34cd":{
			"role": "Guest"
		}
	}
}
# JSON 字段类型

Spring 在记录字段时,REST DOCS 试图通过检查有效负载来确定其类型。支持七种不同的类型:

Type Description
array 字段的每一次出现的值都是一个数组。
boolean 字段的每个出现的值都是布尔(truefalse)。
object 字段的每一次出现的值都是一个对象。
number 字段的每一次出现的值都是一个数字。
null 字段的每个出现的值是null
string 字段的每一次出现的值都是一个字符串。
varies 该场在有效载荷中多次出现,具有各种不同的类型。

还可以使用FieldDescriptor上的type(Object)方法显式地设置类型。文档中使用了提供的Object方法的toString的结果。通常,使用JsonFieldType枚举的值之一。下面的例子说明了如何做到这一点:

MockMVC

.andDo(document("index", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
		.description("The user's email address"))));
1 将字段的类型设置为String

WebTestClient

.consumeWith(document("user",
	responseFields(
		fieldWithPath("contact.email")
			.type(JsonFieldType.STRING) (1)
			.description("The user's email address"))));
1 将字段的类型设置为String

放心吧

.filter(document("user", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
		.description("The user's email address"))))
1 将字段的类型设置为String
# XML 有效负载

本节介绍如何使用 XML 有效负载。

# XML 字段路径

使用 XPath 描述 XML 字段路径。/用于下降到子节点。

# XML 字段类型

在记录 XML 有效负载时,必须使用FieldDescriptor上的type(Object)方法为字段提供类型。文档中使用了所提供类型的toString方法的结果。

# 重用字段描述符

除了对重用片段的一般支持外,请求和响应片段还允许使用路径前缀配置其他描述符。这使得请求或响应有效负载的重复部分的描述符可以创建一次,然后重用。

考虑返回一本书的端点:

{
	"title": "Pride and Prejudice",
	"author": "Jane Austen"
}

titleauthor的路径分别是titleauthor

现在考虑一个返回一组图书的端点:

[{
	"title": "Pride and Prejudice",
	"author": "Jane Austen"
},
{
	"title": "To Kill a Mockingbird",
	"author": "Harper Lee"
}]

titleauthor的路径分别是[].title[].author。单本书和书数组之间的唯一区别是,字段的路径现在有一个[].前缀。

你可以创建描述符来记录一本书,如下所示:

FieldDescriptor[] book = new FieldDescriptor[] { fieldWithPath("title").description("Title of the book"),
		fieldWithPath("author").description("Author of the book") };

然后,你可以使用它们来记录一本书,如下所示:

MockMVC

this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("book", responseFields(book))); (1)
1 通过使用现有描述符,文档titleauthor

WebTestClient

this.webTestClient.get().uri("/books/1").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("book",
		responseFields(book))); (1)
1 通过使用现有描述符,文档titleauthor

放心吧

RestAssured.given(this.spec).accept("application/json").filter(document("book", responseFields(book))) (1)
		.when().get("/books/1").then().assertThat().statusCode(is(200));
1 通过使用现有描述符,文档titleauthor

你还可以使用描述符记录一组图书,如下所示:

MockMVC

this.mockMvc.perform(get("/books").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("book", responseFields(fieldWithPath("[]").description("An array of books")) (1)
				.andWithPrefix("[].", book))); (2)
1 记录该数组。
2 文档[].title[].author使用带[].前缀的现有描述符

WebTestClient

this.webTestClient.get().uri("/books").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("books",
		responseFields(
			fieldWithPath("[]")
				.description("An array of books")) (1)
				.andWithPrefix("[].", book))); (2)
1 记录该数组。
2 文档[].title[].author使用带[].前缀的现有描述符

放心吧

RestAssured.given(this.spec).accept("application/json")
		.filter(document("books", responseFields(fieldWithPath("[]").description("An array of books")) (1)
				.andWithPrefix("[].", book))) (2)
		.when().get("/books").then().assertThat().statusCode(is(200));
1 记录该数组。
2 文档[].title[].author使用带[].前缀的现有描述符

# 记录请求或响应有效负载的一个分节

如果有效载荷很大或结构复杂,那么记录有效载荷的各个部分可能会很有用。REST DOCS 允许你通过提取有效负载的一个小节,然后将其记录下来来实现这一点。

# 记录请求或响应机构的一个部分

考虑以下 JSON 响应主体:

{
	"weather": {
		"wind": {
			"speed": 15.3,
			"direction": 287.0
		},
		"temperature": {
			"high": 21.2,
			"low": 14.8
		}
	}
}

你可以生成一个片段来记录temperature对象,如下所示:

MockMVC

this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("location", responseBody(beneathPath("weather.temperature")))); (1)
1 org.springframework.restdocs.payload.PayloadDocumentation上使用静态responseBodybeneathPath方法。
为请求体产生一个片段。
可以使用requestBody代替responseBody

WebTestClient

this.webTestClient.get().uri("/locations/1").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("temperature",
		responseBody(beneathPath("weather.temperature")))); (1)
1 org.springframework.restdocs.payload.PayloadDocumentation上使用静态responseBodybeneathPath方法。
为请求主体生成一个片段。可以使用requestBody代替responseBody

放心吧

RestAssured.given(this.spec).accept("application/json")
		.filter(document("location", responseBody(beneathPath("weather.temperature")))) (1)
		.when().get("/locations/1").then().assertThat().statusCode(is(200));
1 生成包含响应体的一个分节的片段。
responseBodybeneathPath方法上使用静态org.springframework.restdocs.payload.PayloadDocumentation
为请求体生成一个片段,可以使用requestBody代替responseBody

结果是一个包含以下内容的片段:

{
	"temperature": {
		"high": 21.2,
		"low": 14.8
	}
}

为了使代码片段的名称不同,还包含了该小节的标识符。默认情况下,这个标识符是beneath-${path}。例如,前面的代码会产生一个名为response-body-beneath-weather.temperature.adoc的代码片段。你可以使用withSubsectionId(String)方法自定义标识符,如下所示:

responseBody(beneathPath("weather.temperature").withSubsectionId("temp"));

结果是一个名为request-body-temp.adoc的片段。

# 记录请求或响应的一个分节的字段

除了记录请求或响应主体的一个小节外,你还可以记录特定小节中的字段。你可以生成一个片段,该片段记录temperature对象(highlow)的字段,如下所示:

MockMVC

this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("location", responseFields(beneathPath("weather.temperature"), (1)
				fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
				fieldWithPath("low").description("The forecast low in degrees celcius"))));
1 在路径weather.temperature下的响应有效负载的小节中生成一个描述字段的片段。
beneathPath上使用静态beneathPath方法。
2 记录highlow字段。

WebTestClient

this.webTestClient.get().uri("/locations/1").accept(MediaType.APPLICATION_JSON)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("temperature",
		responseFields(beneathPath("weather.temperature"), (1)
			fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
			fieldWithPath("low").description("The forecast low in degrees celcius"))));
1 在路径weather.temperature下的响应有效负载的小节中生成一个描述字段的片段。
beneathPath上使用静态beneathPath方法。
2 记录highlow字段。

放心吧

RestAssured.given(this.spec).accept("application/json")
		.filter(document("location", responseFields(beneathPath("weather.temperature"), (1)
				fieldWithPath("high").description("The forecast high in degrees celcius"), (2)
				fieldWithPath("low").description("The forecast low in degrees celcius"))))
		.when().get("/locations/1").then().assertThat().statusCode(is(200));
1 在路径weather.temperature下的响应有效负载的小节中生成一个描述字段的片段。
beneathPath上使用静态beneathPath方法。
2 记录highlow字段。

结果是一个片段,其中包含一个表,该表描述highlowweather.temperature字段。为了使代码片段的名称不同,还包含了该小节的标识符。默认情况下,这个标识符是beneath-${path}。例如,前面的代码会产生一个名为response-fields-beneath-weather.temperature.adoc的代码片段。

# 请求参数

你可以使用requestParameters记录请求的参数。你可以在GET请求的查询字符串中包含请求参数。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(get("/users?page=2&per_page=100")) (1)
		.andExpect(status().isOk()).andDo(document("users", requestParameters((2)
				parameterWithName("page").description("The page to retrieve"), (3)
				parameterWithName("per_page").description("Entries per page") (4)
		)));
1 在查询字符串中,使用两个参数GETper_page执行GET请求。
2 配置 Spring REST DOCS 以生成描述请求参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParameters方法。
3 记录page参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
4 记录per_page参数。

WebTestClient

this.webTestClient.get().uri("/users?page=2&per_page=100") (1)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("users", requestParameters((2)
			parameterWithName("page").description("The page to retrieve"), (3)
			parameterWithName("per_page").description("Entries per page") (4)
	)));
1 在查询字符串中,使用两个参数GETper_page执行GET请求。
2 配置 Spring REST DOCS 以生成描述请求参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParameters方法。
3 记录page参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
4 记录per_page参数。

放心吧

RestAssured.given(this.spec).filter(document("users", requestParameters((1)
		parameterWithName("page").description("The page to retrieve"), (2)
		parameterWithName("per_page").description("Entries per page")))) (3)
		.when().get("/users?page=2&per_page=100") (4)
		.then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述请求参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParameters方法。
2 记录page参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
3 记录per_page参数。
4 在查询字符串中,使用两个参数GETper_page执行GET请求。

还可以将请求参数作为表单数据包含在 POST 请求的主体中。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(post("/users").param("username", "Tester")) (1)
		.andExpect(status().isCreated()).andDo(document("create-user",
				requestParameters(parameterWithName("username").description("The user's username"))));
1 使用单个参数POST执行username请求。

WebTestClient

MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "Tester");
this.webTestClient.post().uri("/users").body(BodyInserters.fromFormData(formData)) (1)
	.exchange().expectStatus().isCreated().expectBody()
	.consumeWith(document("create-user", requestParameters(
		parameterWithName("username").description("The user's username")
)));
1 使用单个参数执行POST请求,username

放心吧

RestAssured.given(this.spec)
		.filter(document("create-user",
				requestParameters(parameterWithName("username").description("The user's username"))))
		.formParam("username", "Tester") (1)
		.when().post("/users") (2)
		.then().assertThat().statusCode(is(200));
1 配置username参数。
2 执行POST请求。

在所有情况下,结果都是一个名为request-parameters.adoc的片段,其中包含一个表,该表描述了资源所支持的参数。

在记录请求参数时,如果在请求中使用了未记录的请求参数,则测试将失败。类似地,如果在请求中找不到已记录的请求参数,并且该请求参数未标记为可选的,则测试也会失败。

如果不想记录请求参数,可以将其标记为“忽略”。这可以防止它出现在生成的代码片段中,同时避免上述的故障。

你还可以在放松模式中记录请求参数,在这种模式中,任何未记录的参数都不会导致测试失败。要做到这一点,请在org.springframework.restdocs.request.RequestDocumentation上使用relaxedRequestParameters方法。在记录一个特定的场景时,如果你只想关注请求参数的一个子集,这可能会很有用。

# 路径参数

你可以使用pathParameters记录请求的路径参数。下面的例子说明了如何做到这一点:

MockMvc

this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) (1)
		.andExpect(status().isOk()).andDo(document("locations", pathParameters((2)
				parameterWithName("latitude").description("The location's latitude"), (3)
				parameterWithName("longitude").description("The location's longitude") (4)
		)));
1 使用两个路径参数GETlongitude执行GET请求。
2 配置 Spring REST DOCS 以生成描述请求的路径参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态pathParameters方法。
3 记录名为latitude的参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
4 记录名为longitude的参数。

WebTestClient

this.webTestClient.get().uri("/locations/{latitude}/{longitude}", 51.5072, 0.1275) (1)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("locations",
		pathParameters((2)
			parameterWithName("latitude").description("The location's latitude"), (3)
			parameterWithName("longitude").description("The location's longitude")))); (4)
1 使用两个路径参数执行GET请求,latitudelongitude
2 配置 Spring REST DOCS 以生成描述请求的路径参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态pathParameters方法。
3 记录名为latitude的参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
4 记录名为longitude的参数。

放心吧

RestAssured.given(this.spec).filter(document("locations", pathParameters((1)
		parameterWithName("latitude").description("The location's latitude"), (2)
		parameterWithName("longitude").description("The location's longitude")))) (3)
		.when().get("/locations/{latitude}/{longitude}", 51.5072, 0.1275) (4)
		.then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述请求的路径参数的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态pathParameters方法。
2 记录名为latitude的参数。
org.springframework.restdocs.request.RequestDocumentation上使用静态parameterWithName方法。
3 记录名为longitude的参数。
4 使用两个路径参数GETlongitude执行GET请求。

结果是一个名为path-parameters.adoc的片段,其中包含一个表,该表描述了资源所支持的路径参数。

如果使用 MockMVC,要使路径参数对文档可用,你必须使用RestDocumentationRequestBuilders而不是MockMvcRequestBuilders上的一个方法来构建请求。

在记录路径参数时,如果在请求中使用了未记录的路径参数,则测试将失败。类似地,如果在请求中找不到文档化的路径参数,并且路径参数未标记为可选的,则测试也会失败。

你还可以在放松模式中记录路径参数,在这种模式中,任何未记录的参数都不会导致测试失败。要做到这一点,请在org.springframework.restdocs.request.RequestDocumentation上使用relaxedPathParameters方法。在记录一个特定的场景时,如果你只想关注路径参数的一个子集,这可能会很有用。

如果不想记录路径参数,可以将其标记为“忽略”。这样做可以防止它出现在生成的代码片段中,同时避免前面描述的故障。

# 请求零件

你可以使用requestParts来记录多部分请求的各个部分。下面的示例展示了如何做到这一点:

MockMvc

this.mockMvc.perform(multipart("/upload").file("file", "example".getBytes())) (1)
		.andExpect(status().isOk()).andDo(document("upload", requestParts((2)
				partWithName("file").description("The file to upload")) (3)
		));
1 使用一个名为file的部件执行POST请求。
2 配置 Spring REST DOCS 以生成描述请求部分的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParts方法。
3 记录名为file的部分。
org.springframework.restdocs.request.RequestDocumentation上使用静态partWithName方法。

WebTestClient

MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
multipartData.add("file", "example".getBytes());
this.webTestClient.post().uri("/upload").body(BodyInserters.fromMultipartData(multipartData)) (1)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("upload", requestParts((2)
		partWithName("file").description("The file to upload")) (3)
));
1 使用一个名为file的部件执行POST请求。
2 配置 Spring REST DOCS 以生成描述请求部分的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParts方法。
3 记录名为file的部分。
partWithName上使用静态partWithName方法。

放心吧

RestAssured.given(this.spec).filter(document("users", requestParts((1)
		partWithName("file").description("The file to upload")))) (2)
		.multiPart("file", "example") (3)
		.when().post("/upload") (4)
		.then().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述请求部分的片段。
org.springframework.restdocs.request.RequestDocumentation上使用静态requestParts方法。
2 记录名为file的部分。
org.springframework.restdocs.request.RequestDocumentation上使用静态partWithName方法。
3 用名为file的部分配置请求。
4 执行POST请求到/upload

结果是一个名为request-parts.adoc的片段,其中包含一个表,该表描述了资源支持的请求部分。

在记录请求部分时,如果在请求中使用了未记录的部分,则测试将失败。类似地,如果在请求中找不到已记录的部分,并且该部分未标记为可选的,则测试也会失败。

你还可以在放松模式中记录请求部分,在这种模式中,任何未记录的部分都不会导致测试失败。要做到这一点,请在org.springframework.restdocs.request.RequestDocumentation上使用relaxedRequestParts方法。在记录一个特定的场景时,如果你只想关注请求部分的一个子集,这可能会很有用。

如果不想记录请求部分,可以将其标记为“忽略”。这可以防止它出现在生成的代码片段中,同时避免前面描述的故障。

# 请求部分有效载荷

你可以用与请求的有效载荷几乎相同的方式记录请求部分的有效负载,并支持记录请求部分的主体及其字段。

# 记录请求部分的主体

你可以生成一个包含请求部分主体的片段,如下所示:

MockMvc

MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png", "<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "", "application/json",
		"{ \"version\": \"1.0\"}".getBytes());

this.mockMvc.perform(multipart("/images").file(image).file(metadata).accept(MediaType.APPLICATION_JSON))
		.andExpect(status().isOk()).andDo(document("image-upload", requestPartBody("metadata"))); (1)
1 配置 Spring REST DOCS 以生成包含名为metadata的请求部分的主体的片段。
requestPartBody上使用静态requestPartBody方法。

WebTestClient

MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
Resource imageResource = new ByteArrayResource("<<png data>>".getBytes()) {

	@Override
	public String getFilename() {
		return "image.png";
	}

};
multipartData.add("image", imageResource);
multipartData.add("metadata", Collections.singletonMap("version",  "1.0"));

this.webTestClient.post().uri("/images").body(BodyInserters.fromMultipartData(multipartData))
	.accept(MediaType.APPLICATION_JSON).exchange()
	.expectStatus().isOk().expectBody()
	.consumeWith(document("image-upload",
			requestPartBody("metadata"))); (1)
1 配置 Spring REST DOCS 以生成包含名为metadata的请求部分的主体的片段。
PayloadDocumentation上使用静态requestPartBody方法。

放心吧

Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
		.filter(document("image-upload", requestPartBody("metadata"))) (1)
		.when().multiPart("image", new File("image.png"), "image/png").multiPart("metadata", metadata)
		.post("images").then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成包含名为metadata的请求部分的主体的片段。
PayloadDocumentation上使用静态requestPartBody方法。

结果是一个名为request-part-${part-name}-body.adoc的片段,其中包含了该零件的主体。例如,记录一个名为metadata的部分会产生一个名为request-part-metadata-body.adoc的片段。

# 记录请求部分的字段

你可以用与请求或响应的字段相同的方式记录请求部分的字段,如下所示:

MockMvc

MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png", "<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "", "application/json",
		"{ \"version\": \"1.0\"}".getBytes());

this.mockMvc.perform(multipart("/images").file(image).file(metadata).accept(MediaType.APPLICATION_JSON))
		.andExpect(status().isOk()).andDo(document("image-upload", requestPartFields("metadata", (1)
				fieldWithPath("version").description("The version of the image")))); (2)
1 配置 Spring REST DOCS 以生成描述在名为metadata的请求部分的有效负载中的字段的片段。
requestPartFields上使用静态requestPartFields方法。
2 期望具有路径version的字段。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态fieldWithPath方法。

WebTestClient

MultiValueMap<String, Object> multipartData = new LinkedMultiValueMap<>();
Resource imageResource = new ByteArrayResource("<<png data>>".getBytes()) {

	@Override
	public String getFilename() {
		return "image.png";
	}

};
multipartData.add("image", imageResource);
multipartData.add("metadata", Collections.singletonMap("version",  "1.0"));
this.webTestClient.post().uri("/images").body(BodyInserters.fromMultipartData(multipartData))
	.accept(MediaType.APPLICATION_JSON).exchange()
	.expectStatus().isOk().expectBody()
	.consumeWith(document("image-upload",
		requestPartFields("metadata", (1)
			fieldWithPath("version").description("The version of the image")))); (2)
1 配置 Spring REST DOCS 以生成描述在名为metadata的请求部分的有效负载中的字段的片段。
requestPartFields上使用静态requestPartFields方法。
2 期望具有路径version的字段。
fieldWithPath上使用静态fieldWithPath方法。

放心吧

Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
		.filter(document("image-upload", requestPartFields("metadata", (1)
				fieldWithPath("version").description("The version of the image")))) (2)
		.when().multiPart("image", new File("image.png"), "image/png").multiPart("metadata", metadata)
		.post("images").then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述在名为metadata的请求部分的有效负载中的字段的片段。
requestPartFields上使用静态requestPartFields方法。
2 期望具有路径version的字段。
org.springframework.restdocs.payload.PayloadDocumentation上使用静态fieldWithPath方法。

其结果是一个包含描述该部分字段的表的片段。这个片段被命名为request-part-${part-name}-fields.adoc。例如,记录一个名为metadata的部分会产生一个名为request-part-metadata-fields.adoc的片段。

在记录字段时,如果在零件的有效负载中发现了未记录的字段,则测试将失败。类似地,如果在零件的有效载荷中没有找到已记录的字段,并且该字段未标记为可选字段,则测试也会失败。对于具有层次结构的有效负载,记录一个字段就足以使其所有后代也被视为已被记录。

如果不想记录某个字段,可以将其标记为“忽略”。这样做可以防止它出现在生成的代码片段中,同时避免上述的故障。

你还可以在放松模式下记录字段,在这种模式下,任何未记录的字段都不会导致测试失败。要做到这一点,请在org.springframework.restdocs.payload.PayloadDocumentation上使用relaxedRequestPartFields方法。在记录特定场景时,如果你只想关注部件有效负载的一个子集,这可能会很有用。

有关描述字段、记录使用 XML 的有效负载的更多信息,请参见关于记录请求和响应有效载荷的部分

# HTTP 头

你可以通过分别使用requestHeadersresponseHeaders来记录请求或响应中的头。下面的例子说明了如何做到这一点:

MockMvc

this.mockMvc.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) (1)
		.andExpect(status().isOk()).andDo(document("headers", requestHeaders((2)
				headerWithName("Authorization").description("Basic auth credentials")), (3)
				responseHeaders((4)
						headerWithName("X-RateLimit-Limit")
								.description("The total number of requests permitted per period"),
						headerWithName("X-RateLimit-Remaining")
								.description("Remaining requests permitted in current period"),
						headerWithName("X-RateLimit-Reset")
								.description("Time at which the rate limit period will reset"))));
1 使用使用基本身份验证的Authorization报头执行GET请求。
2 配置 Spring REST DOCS 以生成描述请求头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态requestHeaders方法。
3 记录Authorization标头。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态headerWithName方法。
4 生成一个描述响应头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态responseHeaders方法。

WebTestClient

this.webTestClient
	.get().uri("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=") (1)
	.exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("headers",
		requestHeaders((2)
			headerWithName("Authorization").description("Basic auth credentials")), (3)
		responseHeaders((4)
			headerWithName("X-RateLimit-Limit")
				.description("The total number of requests permitted per period"),
			headerWithName("X-RateLimit-Remaining")
				.description("Remaining requests permitted in current period"),
			headerWithName("X-RateLimit-Reset")
				.description("Time at which the rate limit period will reset"))));
1 使用使用基本身份验证的Authorization报头执行GET请求。
2 配置 Spring REST DOCS 以生成描述请求头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态requestHeaders方法。
3 记录Authorization标头。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态headerWithName方法。
4 生成一个描述响应头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态responseHeaders方法。

REST Assured

RestAssured.given(this.spec).filter(document("headers", requestHeaders((1)
		headerWithName("Authorization").description("Basic auth credentials")), (2)
		responseHeaders((3)
				headerWithName("X-RateLimit-Limit")
						.description("The total number of requests permitted per period"),
				headerWithName("X-RateLimit-Remaining")
						.description("Remaining requests permitted in current period"),
				headerWithName("X-RateLimit-Reset")
						.description("Time at which the rate limit period will reset"))))
		.header("Authorization", "Basic dXNlcjpzZWNyZXQ=") (4)
		.when().get("/people").then().assertThat().statusCode(is(200));
1 配置 Spring REST DOCS 以生成描述请求头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态requestHeaders方法。
2 记录Authorization头文件。
org.springframework.restdocs.headers.headerdocumentation 上使用静态headerWithName`方法。
3 生成一个描述响应头的片段。
org.springframework.restdocs.headers.HeaderDocumentation上使用静态responseHeaders方法。
4 用使用基本身份验证的Authorization头配置请求。

结果是一个名为request-headers.adoc的片段和一个名为response-headers.adoc的片段。每个表都包含一个描述标题的表。

在记录 HTTP 头文件时,如果在请求或响应中找不到已记录的头文件,则测试将失败。

# 重用片段

对于正在文档中的 API 来说,具有一些在其多个资源中通用的特性是很常见的。为了避免在记录此类资源时出现重复,你可以重用配置有公共元素的Snippet

首先,创建描述公共元素的Snippet。下面的示例展示了如何做到这一点:

protected final LinksSnippet pagingLinks = links(
		linkWithRel("first").optional().description("The first page of results"),
		linkWithRel("last").optional().description("The last page of results"),
		linkWithRel("next").optional().description("The next page of results"),
		linkWithRel("prev").optional().description("The previous page of results"));

其次,使用这个片段并添加更多特定于资源的描述符。下面的例子说明了如何做到这一点:

MockMvc

this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
		.andDo(document("example", this.pagingLinks.and((1)
				linkWithRel("alpha").description("Link to the alpha resource"),
				linkWithRel("bravo").description("Link to the bravo resource"))));
1 重用pagingLinks``Snippet,调用and以添加特定于被记录的资源的描述符。

WebTestClient

this.webTestClient.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange()
	.expectStatus().isOk().expectBody()
	.consumeWith(document("example", this.pagingLinks.and((1)
		linkWithRel("alpha").description("Link to the alpha resource"),
		linkWithRel("bravo").description("Link to the bravo resource"))));
1 重用pagingLinks``Snippet,调用and以添加特定于被记录的资源的描述符。

REST Assured

RestAssured.given(this.spec).accept("application/json").filter(document("example", this.pagingLinks.and((1)
		linkWithRel("alpha").description("Link to the alpha resource"),
		linkWithRel("bravo").description("Link to the bravo resource")))).get("/").then().assertThat()
		.statusCode(is(200));
1 重用pagingLinks``Snippet,调用and以添加特定于被记录的资源的描述符。

该示例的结果是,带有rel值的firstlastnextpreviousalphabravo的链接都是有文档记录的。

# 记录约束

Spring REST DOCS 提供了许多类,这些类可以帮助你记录约束。你可以使用ConstraintDescriptions的实例来访问类的约束的描述。下面的示例展示了如何做到这一点:

public void example() {
	ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class); (1)
	List<String> descriptions = userConstraints.descriptionsForProperty("name"); (2)
}

static class UserInput {

	@NotNull
	@Size(min = 1)
	String name;

	@NotNull
	@Size(min = 8)
	String password;

}
1 UserInput类创建ConstraintDescriptions实例。
2 获取name属性约束的描述。
此列表包含两个描述:一个用于NotNull约束,另一个用于Size约束。

Spring Hateoas 示例中的[ApiDocumentation](https://github.com/ Spring-projects/ Spring-restdocs/tree/v2.0.6.release/samples/rest-notes- Spring-hateoas/SRC/test/java/com/example/notes/apidocumentation.java)类显示了这一功能。

# 寻找约束

默认情况下,通过使用 Bean 验证Validator来找到约束。目前,只支持属性约束。你可以通过使用自定义的ValidatorConstraintResolver实例创建ConstraintDescriptions来定制Validator所使用的Validator。要完全控制约束解析,可以使用自己的ConstraintResolver实现。

# 描述约束

对于 Bean Validation2.0 的所有约束,都提供了缺省描述:

  • AssertFalse

  • AssertTrue

  • DecimalMax

  • DecimalMin

  • Digits

  • Email

  • Future

  • FutureOrPresent

  • Max

  • Min

  • Negative

  • NegativeOrZero

  • NotBlank

  • NotEmpty

  • NotNull

  • Null

  • Past

  • PastOrPresent

  • Pattern

  • Positive

  • PositiveOrZero

  • Size

Hibernate Validator 还提供了以下约束的默认描述:

  • CodePointLength

  • CreditCardNumber

  • Currency

  • EAN

  • Email

  • Length

  • LuhnCheck

  • Mod10Check

  • Mod11Check

  • NotBlank

  • NotEmpty

  • Currency

  • Range

  • SafeHtml

  • URL

要重写缺省描述或提供新的描述,你可以创建一个基名为org.springframework.restdocs.constraints.ConstraintDescriptions的资源包。 Spring 基于 Hateoas 的样本包含这样的资源包的一个例子 (opens new window)

资源包中的每个键都是约束的完全限定名加上.description。例如,标准@NotNull约束的键是javax.validation.constraints.NotNull.description

你可以在约束的描述中使用引用约束属性的属性占位符。例如,@Min约束的默认描述Must be at least ${value}是指约束的value属性。

要获得约束描述解析的更多控制,可以使用自定义ConstraintDescriptions创建ResourceBundleConstraintDescriptionResolver。要获得完全的控制,你可以使用自定义的ConstraintDescriptions实现来创建ConstraintDescriptionResolver

# 在生成的代码片段中使用约束描述

一旦有了约束的描述,你就可以在生成的代码片段中随意使用它们。例如,你可能希望将约束描述作为字段描述的一部分。或者,你可以在请求字段片段中以额外信息的形式包含约束。 Spring 基于 Hateoas 的示例中的[ApiDocumentation](https://github.com/ Spring-projects/ Spring-restdocs/tree/v2.0.6.release/samples/rest-notes- Spring-hateoas/SRC/test/java/com/example/notes/apidocumentation.java)类说明了后一种方法。

# 默认片段

当你记录请求和响应时,会自动生成许多代码片段。

Snippet 说明
curl-request.adoc 包含[curl](https://curl.haxx.se)命令,该命令与文档中的MockMvc调用等价。
httpie-request.adoc 包含[HTTPie](https://httpie.org)命令,该命令相当于正在记录的MockMvc调用。
http-request.adoc 包含与正在记录的MockMVC调用等价的 HTTP 请求。
http-response.adoc 包含返回的 HTTP 响应。
request-body.adoc 包含已发送的请求的主体。
response-body.adoc 包含返回的响应的主体。

你可以配置默认情况下产生的代码段。有关更多信息,请参见配置部分

# 使用参数化输出目录

当使用 MockMVC、Rest Assured 或WebTestClient时,你可以参数化document使用的输出目录。使用WebTestClient参数化输出需要 Spring Framework5.3.5 或更高版本。

支持以下参数:

Parameter 说明
{methodName} 测试方法的未修改名称。
{method-name} 测试方法的名称,使用 kebab-case 格式化。
{method_name} 测试方法的名称,使用 snake_case 格式化。
{ClassName} 测试类的未修改的简单名称。
{class-name} 测试类的简单名称,使用 kebab-case 格式化。
{class_name} 测试类的简单名称,使用 Snake_case 格式化。
{step} 当前测试中对服务的调用次数。

例如,document("{class-name}/{method-name}")在一个名为creatingANote的测试方法中,在测试类GettingStartedDocumentation上将片段写入一个名为getting-started-documentation/creating-a-note的目录中。

参数化的输出目录与@Before方法结合使用时特别有用。它允许在设置方法中配置文档一次,然后在类中的每个测试中重用。下面的例子说明了如何做到这一点:

MockMVC

@Before
public void setUp() {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
			.apply(documentationConfiguration(this.restDocumentation)).alwaysDo(document("{method-name}/{step}/"))
			.build();
}

放心吧

@Before
public void setUp() {
	this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation))
			.addFilter(document("{method-name}/{step}")).build();
}

WebTestClient

@Before
public void setUp() {
	this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
			.filter(documentationConfiguration(this.restDocumentation))
			.entityExchangeResultConsumer(document("{method-name}/{step}")).build();
}

有了这种配置,对你正在测试的服务的每次调用都会产生默认片段,而不需要进行任何进一步的配置。看看每个示例应用程序中的GettingStartedDocumentation类,就可以看到该功能的实际应用。

# 自定义输出

本节描述如何自定义 Spring REST DOCS 的输出。

# 自定义生成的代码片段

Spring REST DOCS 使用模板来产生所生成的片段。是为 Spring REST DOCS 能够产生的每个片段提供的。要定制片段的内容,你可以提供自己的模板。

模板是从 Classpath 从org.springframework.restdocs.templates子包中加载的。子包的名称由所使用的模板格式的 ID 确定。默认的模板格式 ASCIIDoctor 的 ID 为asciidoctor,因此从org.springframework.restdocs.templates.asciidoctor加载片段。每个模板都以其生成的代码片段命名。例如,要覆盖curl-request.adoc片段的模板,请在src/test/resources/org/springframework/restdocs/templates/asciidoctor中创建一个名为curl-request.snippet的模板。

# 包括额外的信息

有两种方法可以提供额外的信息以包含在生成的代码片段中:

  • 使用描述符上的attributes方法向其添加一个或多个属性。

  • 在调用curlRequesthttpRequesthttpResponse时传入一些属性,以此类推。这样的属性与代码片段作为一个整体相关联。

在模板呈现过程中,任何附加属性都是可用的。再加上自定义的代码片段模板,这使得在生成的代码片段中包含额外的信息成为可能。

一个具体的例子是,在记录请求字段时,添加了一个约束列和一个标题。第一步是为你记录的每个字段提供constraints属性,并提供title属性。下面的例子说明了如何做到这一点:

MockMVC

.andDo(document("create-user", requestFields(attributes(key("title").value("Fields for user creation")), (1)
		fieldWithPath("name").description("The user's name")
				.attributes(key("constraints").value("Must not be null. Must not be empty")), (2)
		fieldWithPath("email").description("The user's email address")
				.attributes(key("constraints").value("Must be a valid email address"))))); (3)
1 为请求字段片段配置title属性。
2 name字段设置constraints属性。
3 email字段设置constraints属性。

WebTestClient

.consumeWith(document("create-user",
	requestFields(
		attributes(key("title").value("Fields for user creation")), (1)
		fieldWithPath("name")
			.description("The user's name")
			.attributes(key("constraints").value("Must not be null. Must not be empty")), (2)
		fieldWithPath("email")
			.description("The user's email address")
			.attributes(key("constraints").value("Must be a valid email address"))))); (3)
1 为请求字段片段配置title属性。
2 name字段设置constraints属性。
3 email字段设置constraints属性。

放心吧

.filter(document("create-user",
		requestFields(attributes(key("title").value("Fields for user creation")), (1)
				fieldWithPath("name").description("The user's name")
						.attributes(key("constraints").value("Must not be null. Must not be empty")), (2)
				fieldWithPath("email").description("The user's email address")
						.attributes(key("constraints").value("Must be a valid email address"))))) (3)
1 为请求字段片段配置title属性。
2 name字段设置constraints属性。
3 email字段设置constraints属性。

第二步是提供一个名为request-fields.snippet的自定义模板,该模板在生成的代码片段的表中包含有关字段约束的信息,并添加一个标题。下面的示例展示了如何做到这一点:

.{{title}} (1)
|===
|Path|Type|Description|Constraints (2)

{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{constraints}} (3)

{{/fields}}
|===
1 将标题添加到表格中。
2 添加一个名为“约束”的新列。
3 在表的每一行中包含描述符的constraints属性。

# 定制请求和响应

在某些情况下,你可能不希望将请求记录为与发送的请求完全一致,或者不希望将响应记录为与接收的请求完全一致。 Spring REST DOCS 提供了许多可用于在请求或响应被文档化之前对其进行修改的预处理器。

通过调用documentOperationRequestPreprocessorOperationResponsePreprocessor来配置预处理。可以通过在Preprocessors上使用静态preprocessRequestpreprocessResponse方法获得实例。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(get("/")).andExpect(status().isOk())
		.andDo(document("index", preprocessRequest(removeHeaders("Foo")), (1)
				preprocessResponse(prettyPrint()))); (2)
1 应用一个请求预处理程序,删除名为Foo的标头。
2 应用一个响应预处理器来打印它的内容。

WebTestClient

this.webTestClient.get().uri("/").exchange().expectStatus().isOk().expectBody()
	.consumeWith(document("index",
		preprocessRequest(removeHeaders("Foo")), (1)
		preprocessResponse(prettyPrint()))); (2)
1 应用一个请求预处理程序来删除名为Foo的头。
2 应用一个响应预处理器来打印它的内容。

放心吧

RestAssured.given(this.spec).filter(document("index", preprocessRequest(removeHeaders("Foo")), (1)
		preprocessResponse(prettyPrint()))) (2)
		.when().get("/").then().assertThat().statusCode(is(200));
1 应用一个请求预处理程序来删除名为Foo的标头。
2 应用一个响应预处理器来打印它的内容。

或者,你可能希望将相同的预处理器应用于每个测试。可以通过在@Before方法中使用RestDocumentationConfigurerAPI 来配置预处理器。例如,要从所有请求中删除Foo标头并漂亮地打印所有响应,你可以执行以下操作之一(取决于你的测试环境):

MockMVC

private MockMvc mockMvc;

@Before
public void setup() {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
			.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
					.withRequestDefaults(removeHeaders("Foo")) (1)
					.withResponseDefaults(prettyPrint())) (2)
			.build();
}
1 应用一个请求预处理程序,删除名为Foo的头。
2 应用一个响应预处理器来打印它的内容。

WebTestClient

private WebTestClient webTestClient;

@Before
public void setup() {
	this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
		.configureClient()
		.filter(documentationConfiguration(this.restDocumentation)
			.operationPreprocessors()
				.withRequestDefaults(removeHeaders("Foo")) (1)
				.withResponseDefaults(prettyPrint())) (2)
		.build();
}
1 应用一个请求预处理程序来删除名为Foo的头。
2 应用一个响应预处理器来打印它的内容。

放心吧

private RequestSpecification spec;

@Before
public void setup() {
	this.spec = new RequestSpecBuilder()
			.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
					.withRequestDefaults(removeHeaders("Foo")) (1)
					.withResponseDefaults(prettyPrint())) (2)
			.build();
}
1 应用一个请求预处理程序,删除名为Foo的头。
2 应用一个响应预处理器来打印它的内容。

然后,在每个测试中,你可以执行特定于该测试的任何配置。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc.perform(get("/")).andExpect(status().isOk())
		.andDo(document("index", links(linkWithRel("self").description("Canonical self link"))));

WebTestClient

this.webTestClient.get().uri("/").exchange().expectStatus().isOk()
	.expectBody().consumeWith(document("index",
		links(linkWithRel("self").description("Canonical self link"))));

放心吧

RestAssured.given(this.spec)
		.filter(document("index", links(linkWithRel("self").description("Canonical self link")))).when()
		.get("/").then().assertThat().statusCode(is(200));

通过Preprocessors上的静态方法,可以获得各种内置的预处理器,包括上面所示的那些。有关更多详细信息,请参见below

# 预处理器

# 漂亮的印刷

prettyPrintonPreprocessors格式化请求或响应的内容,以使其更易于阅读。

# 掩蔽链接

如果你正在记录一个基于超媒体的 API,你可能希望鼓励客户机通过使用链接而不是通过使用硬编码的 URI 来导航该 API。这样做的一种方法是限制在文档中使用 URI。maskLinksonPreprocessors将响应中任何链接的href替换为…​。如果你愿意,你还可以指定一个不同的替代品。

# 删除页眉

removeHeadersonPreprocessors从请求或响应中删除任何标题,其中名称等于任何给定的标题名称。

removeMatchingHeadersonPreprocessors删除请求或响应中名称与给定正则表达式模式匹配的任何标题。

# 替换模式

replacePatternonPreprocessors提供了用于替换请求或响应中的内容的通用机制。任何与正则表达式匹配的事件都将被替换。

# 修改请求参数

你可以在Preprocessors上使用modifyParameters来添加、设置和删除请求参数。

# 修改 URI

如果使用 MockMVC 或未绑定到服务器的 WebTestClient,则应通过更改配置自定义 URI。

你可以在Preprocessors上使用modifyUris来修改请求或响应中的任何 URI。当使用绑定到服务器的 Rest Assured 或 WebTestClient 时,这允许你在测试服务的本地实例时自定义文档中出现的 URI。

# 编写自己的预处理器

如果一个内置的预处理器不能满足你的需求,你可以通过实现OperationPreprocessor接口来编写自己的预处理器。然后,你可以以与任何内置预处理器完全相同的方式使用你的定制预处理器。

如果只想修改请求或响应的内容(主体),可以考虑实现ContentModifier接口,并将其与内置的ContentModifyingOperationPreprocessor一起使用。

# 配置

本节介绍如何配置 Spring REST DOCS。

# 记录的 URI

本节介绍如何配置有文档的 URI。

# MockMVC URI 定制

在使用 MockMVC 时, Spring REST DOCS 记录的 URI 的默认配置如下:

Setting 默认值
Scheme http
Host localhost
Port 8080

此配置由MockMVCRestDocumentationConfigurer应用。你可以使用其 API 更改一个或多个默认值以满足你的需求。下面的示例展示了如何做到这一点:

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation).uris().withScheme("https")
				.withHost("example.com").withPort(443))
		.build();
如果将端口设置为配置方案的默认值(HTTP 的端口为 80,HTTPS 的端口为 443),则在生成的代码段中的任何 URI 中都将省略该端口。
要配置请求的上下文路径,请在MockHttpServletRequestBuilder上使用contextPath方法。

# REST SEART URI 定制

Rest Assured 通过发出实际的 HTTP 请求来测试服务。因此,在对服务执行操作之后,但在对其进行文档记录之前,必须对 URI 进行自定义。aRest Assured-Specific 预处理器用于此目的。

# WebTestClient URI 自定义

当使用 WebTestClient 时, Spring REST DOCS 记录的 URI 的默认基础是[http://localhost:8080](http://localhost:8080)。你可以通过使用WebTestClient.Builder](https://DOCS. Spring.io/ Spring-framework/DOCS/5.0.x/javadoc-api/org/springframework/test/web/reactive/server/webtestclient.builder.html#baseurl-java.lang.string-)上的[<baseUrl(String)方法来定制这个基础。下面的示例展示了如何做到这一点:

@Before
public void setUp() {
	this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
			.baseUrl("https://api.example.com") (1)
			.filter(documentationConfiguration(this.restDocumentation)).build();
}
1 将已记录的 URI 的基础配置为[https://api.example.com](https://api.example.com)

# 片段编码

默认的代码片段编码是UTF-8。你可以使用RestDocumentationConfigurerAPI 更改默认的代码片段编码。例如,以下示例使用ISO-8859-1:

MockMVC

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation).snippets().withEncoding("ISO-8859-1"))
		.build();

WebTestClient

this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
	.filter(documentationConfiguration(this.restDocumentation)
		.snippets().withEncoding("ISO-8859-1"))
	.build();

放心吧

this.spec = new RequestSpecBuilder()
		.addFilter(documentationConfiguration(this.restDocumentation).snippets().withEncoding("ISO-8859-1"))
		.build();
Spring REST DOCS 将请求或响应的内容转换为String时,如果Content-Type标头中指定的charset是可用的。
如果没有它,使用了 JVM 的默认Charset
通过使用file.encoding系统属性,可以配置 JVM 的默认Charset

# 片断模板格式

默认的代码片段模板格式是 ASCIIDoctor。Markdown 还支持开箱即用。你可以使用RestDocumentationConfigurerAPI 更改默认格式。下面的例子说明了如何做到这一点:

MockMVC

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation).snippets()
				.withTemplateFormat(TemplateFormats.markdown()))
		.build();

WebTestClient

this.webTestClient = WebTestClient.bindToApplicationContext(this.context).configureClient()
	.filter(documentationConfiguration(this.restDocumentation)
		.snippets().withTemplateFormat(TemplateFormats.markdown()))
	.build();

放心吧

this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(this.restDocumentation).snippets()
		.withTemplateFormat(TemplateFormats.markdown())).build();

# 默认片段

默认情况下会产生六个片段:

  • curl-request

  • http-request

  • http-response

  • httpie-request

  • request-body

  • response-body

在安装过程中,可以使用RestDocumentationConfigurerAPI 更改默认的代码段配置。以下示例默认情况下仅生成curl-request片段:

MockMvc

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation).snippets().withDefaults(curlRequest()))
		.build();

WebTestClient

this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
	.configureClient().filter(
		documentationConfiguration(this.restDocumentation)
			.snippets().withDefaults(curlRequest()))
	.build();

放心吧

this.spec = new RequestSpecBuilder()
		.addFilter(documentationConfiguration(this.restDocumentation).snippets().withDefaults(curlRequest()))
		.build();

# 默认操作预处理器

在安装过程中,可以使用RestDocumentationConfigurerAPI 配置默认的请求和响应预处理器。以下示例从所有请求中删除Foo标题,并打印所有响应:

MockMvc

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation).operationPreprocessors()
				.withRequestDefaults(removeHeaders("Foo")) (1)
				.withResponseDefaults(prettyPrint())) (2)
		.build();
1 应用一个请求预处理程序来删除名为Foo的标头。
2 应用一个响应预处理器来打印它的内容。

WebTestClient

this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
	.configureClient()
	.filter(documentationConfiguration(this.restDocumentation)
		.operationPreprocessors()
			.withRequestDefaults(removeHeaders("Foo")) (1)
			.withResponseDefaults(prettyPrint())) (2)
	.build();
1 应用一个请求预处理程序,删除名为Foo的标头。
2 应用一个响应预处理器来打印它的内容。

放心吧

this.spec = new RequestSpecBuilder()
		.addFilter(documentationConfiguration(this.restDocumentation).operationPreprocessors()
				.withRequestDefaults(removeHeaders("Foo")) (1)
				.withResponseDefaults(prettyPrint())) (2)
		.build();
1 应用一个请求预处理程序,删除名为Foo的标头。
2 应用一个响应预处理器来打印它的内容。

# 与 ASCIIDoctor 合作

本节描述了与 Spring REST DOCS 特别相关的与 ASCIIDoctor 合作的方面。

ASCIIDoc 是文档格式。
ASCIIDoctor 是从 ASCIIDoc 文件(以.adoc结尾)生成内容(通常是 HTML)的工具。

# Resources

# 包括片段

本节介绍如何包含 ASCIIDoc 片段。

# 包括一个操作的多个片段

你可以使用名为operation的宏导入为特定操作生成的全部或部分片段。它可以通过在项目的构建配置中包含spring-restdocs-asciidoctor来实现。

宏的目标是操作的名称。在最简单的形式中,你可以使用宏来包含一个操作的所有片段,如下面的示例所示:

operation::index[]

可以使用的操作宏还支持snippets属性。snippets属性来选择应该包含的代码片段。该属性的值是一个以逗号分隔的列表。列表中的每个条目都应该是要包含的片段文件的名称(减去.adoc后缀)。例如,只能包含 curl、HTTP 请求和 HTTP 响应片段,如下例所示:

operation::index[snippets='curl-request,http-request,http-response']

前面的例子相当于下面的例子:

[[example_curl_request]]
== Curl request

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[]
# 章节标题

对于使用operation宏包含的每个片段,都会创建一个带有标题的部分。为以下内建片段提供了默认标题:

片断 Title
curl-request Curl Request
http-request HTTP request
http-response HTTP response
httpie-request HTTPie request
links Links
request-body Request body
request-fields Request fields
response-body Response body
response-fields Response fields

对于上表中未列出的片段,通过用空格替换-字符并将第一个字母大写来生成默认标题。例如,一个名为custom-snippet``will be的片段的标题是“自定义片段”。

你可以使用文档属性自定义默认标题。属性的名称应该是operation-{snippet}-title。例如,要将curl-request片段的标题自定义为“示例请求”,你可以使用以下属性:

:operation-curl-request-title: Example request

# 包括单个片段

包括宏 (opens new window)用于在文档中包含单个片段。可以使用snippets属性(由构建配置中配置的spring-restdocs-asciidoctor自动设置)来引用 snippets 输出目录。下面的示例展示了如何做到这一点:

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

# 自定义表格

许多片段在其默认配置中包含一个表。表的外观可以自定义,可以在包含代码片段时提供一些附加配置,也可以使用自定义代码片段模板。

# 格式化列

ASCIIDoctor 对格式化表格的列 (opens new window)提供了丰富的支持。如下例所示,你可以使用cols属性指定表中列的宽度:

[cols="1,3"] (1)
include::{snippets}/index/links.adoc[]
1 表格的宽度分为两列,第二列的宽度是第一列的三倍。

# 配置标题

你可以使用以.为前缀的行来指定表格的标题。下面的示例展示了如何做到这一点:

.Links (1)
include::{snippets}/index/links.adoc[]
1 表格的标题将是Links

# 避免表格式问题

ASCIIDoctor 使用|字符对表格中的单元格进行划界。如果你希望|出现在单元格的内容中,这可能会导致问题。你可以通过使用反斜杠转义|来避免这个问题——换句话说,使用\|而不是|

所有默认的 ASCIIDoctor 片段模板都使用名为tableCellContent的小胡子 lamba 自动执行这个转义。如果你编写自己的自定义模板,你可能想要使用这个 lamba。下面的示例展示了如何在包含description属性的值的单元格中转义|字符:

| {{#tableCellContent}}{{description}}{{/tableCellContent}}

# 进一步阅读

有关自定义表的更多信息,请参见ASCIIDoctor 用户手册中的表格部分 (opens new window)

# 与 Markdown 合作

本节描述了与 Spring REST DOCS 特别相关的使用 Markdown 的方面。

# 局限性

Markdown 最初是为人们为网络写作而设计的,因此,它并不像 ASCIIDoctor 那样适合编写文档。通常,这些限制可以通过使用另一个构建在 Markdown 之上的工具来克服。

Markdown 没有对表格的官方支持。 Spring REST DOCS 的默认 Markdown 片段模板使用Markdown Extra 的表格格式 (opens new window)

# 包括片段

Markdown 没有内置支持将一个 Markdown 文件包含在另一个文件中。要将生成的 Markdown 片段包含在文档中,你应该使用支持此功能的附加工具。一个特别适合于记录 API 的示例是Slate (opens new window)

# 贡献

Spring REST DOCS 旨在使你能够轻松地为你的 RESTful 服务生成高质量的文档。然而,没有你们的贡献,我们就无法实现这一目标。

# Questions

通过使用spring-restdocs标记,可以在堆栈溢出 (opens new window)上询问有关 Spring REST DOCS 的问题。同样,我们鼓励你通过回答问题来帮助你的同伴 Spring REST DOCS 用户。

# Bugs

如果你认为你发现了一个 bug,请花点时间搜索。如果没有其他人报告了该问题,请打开新的一期 (opens new window)详细描述该问题,并在理想情况下包含一个复制该问题的测试。

# 增强功能

如果你希望对 Spring REST DOCS 进行增强,那么最受欢迎的是拉请求。源代码位于GitHub (opens new window)上。你可能希望搜索现有问题 (opens new window)拉请求 (opens new window),以查看是否已经提出了增强。你可能还希望打开新的一期 (opens new window)在开始工作之前讨论可能的增强。