记录您的 API

本节提供了有关使用 Spring REST Docs 记录 API 的更多详细信息。spring-doc.cadn.net.cn

超媒体

Spring REST Docs 提供了对记录基于 超媒体 的 API 中链接的支持。 以下示例展示了如何使用它:spring-doc.cadn.net.cn

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 的链接。 使用 org.springframework.restdocs.hypermedia.HypermediaDocumentation 上的静态 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 的链接。 使用 org.springframework.restdocs.hypermedia.HypermediaDocumentation 上的静态 linkWithRel 方法。
3 期望一个 relbravo 的链接。
REST Assured
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 的代码片段,其中包含一个描述资源链接的表格。spring-doc.cadn.net.cn

如果响应中的链接包含 title,您可以省略其描述符中的描述,此时将使用 title。 如果您省略了描述,且该链接不包含 title,则将发生错误。

在记录链接时,如果在响应中发现未记录的链接,测试将失败。 同样,如果响应中未找到已记录的链接,且该链接未被标记为可选,测试也会失败。spring-doc.cadn.net.cn

如果您不想记录某个链接,可以将其标记为忽略。 这样做可以防止它出现在生成的代码片段中,同时避免上述失败情况。spring-doc.cadn.net.cn

您也可以在宽松模式下记录链接,此时任何未记录的链接都不会导致测试失败。 为此,请在 org.springframework.restdocs.hypermedia.HypermediaDocumentation 上使用 relaxedLinks 方法。 这在记录特定场景时非常有用,因为您只需关注链接的子集。spring-doc.cadn.net.cn

默认支持两种链接格式:spring-doc.cadn.net.cn

  • Atom:链接应位于名为 links 的数组中。 当响应的内容类型与 application/json 兼容时,默认使用此方式。spring-doc.cadn.net.cn

  • HAL:链接应位于名为 _links 的映射中。 当响应的内容类型与 application/hal+json 兼容时,默认会使用此设置。spring-doc.cadn.net.cn

如果您使用 Atom 或 HAL 格式的链接,但内容类型不同,则可以向 links 提供内置的 LinkExtractor 实现之一。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 方法。
REST Assured
.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 接口的自定义实现,以便从响应中提取链接。spring-doc.cadn.net.cn

与其为每个响应中通用的链接(例如使用 HAL 时的 selfcuries)重复编写文档,不如在概述部分统一说明一次,然后在 API 文档的其余部分忽略它们。 为此,您可以基于 对重用代码片段的支持,将链接描述符添加到一个已预配置为忽略特定链接的代码片段中。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

请求和响应负载

除了前面描述的超媒体特定支持外,还提供了对请求和响应负载的一般文档支持。spring-doc.cadn.net.cn

默认情况下,Spring REST Docs 会自动为请求体和响应体生成代码片段。 这些代码片段分别命名为 request-body.adocresponse-body.adocspring-doc.cadn.net.cn

请求和响应字段

为了提供更详细的请求或响应负载文档,提供了对记录负载字段的支持。spring-doc.cadn.net.cn

考虑以下负载:spring-doc.cadn.net.cn

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

您可以按如下方式记录上一个示例的字段:spring-doc.cadn.net.cn

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 的字段。
REST Assured
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.adocspring-doc.cadn.net.cn

在记录字段时,如果在负载中发现未记录的字段,测试将失败。 同样,如果负载中未找到已记录的字段,且该字段未被标记为可选,测试也会失败。spring-doc.cadn.net.cn

如果您不想为所有字段提供详细的文档,可以对负载的整个子部分进行文档化。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 方法。
REST Assured
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 可用于提供负载特定部分的高层概览。 随后,您可以为子部分生成单独的、更详细的文档。 请参阅 记录请求或响应负载的子部分spring-doc.cadn.net.cn

如果您完全不想记录某个字段或小节,可以将其标记为忽略。 这样可以防止它出现在生成的代码片段中,同时避免前面描述的失败情况。spring-doc.cadn.net.cn

您还可以以宽松模式记录字段,在此模式下,任何未记录的字段都不会导致测试失败。 为此,请在 org.springframework.restdocs.payload.PayloadDocumentation 上使用 relaxedRequestFieldsrelaxedResponseFields 方法。 这在记录特定场景时非常有用,因为您希望仅关注负载的子集。spring-doc.cadn.net.cn

默认情况下,Spring REST Docs 假设您正在记录的负载是 JSON。 如果您想记录 XML 负载,则请求或响应的内容类型必须与 application/xml 兼容。
JSON 负载中的字段

本节介绍如何使用 JSON 负载中的字段。spring-doc.cadn.net.cn

JSON 字段路径

JSON 字段路径使用点表示法或方括号表示法。 点表示法使用 '.' 来分隔路径中的每个键(例如,a.b)。 方括号表示法将每个键包裹在方括号和单引号中(例如,['a']['b'])。 在这两种情况下,[] 用于标识数组。 点表示法更简洁,但使用方括号表示法可以在键名中使用 .(例如,['a.b'])。 这两种不同的表示法可以在同一路径中混合使用(例如,a['b'])。spring-doc.cadn.net.cn

考虑以下 JSON 负载:spring-doc.cadn.net.cn

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

在前面的 JSON 负载中,以下路径均存在:spring-doc.cadn.net.cn

路径

aspring-doc.cadn.net.cn

一个包含 b 的对象spring-doc.cadn.net.cn

a.bspring-doc.cadn.net.cn

包含三个对象的数组spring-doc.cadn.net.cn

['a']['b']spring-doc.cadn.net.cn

包含三个对象的数组spring-doc.cadn.net.cn

a['b']spring-doc.cadn.net.cn

包含三个对象的数组spring-doc.cadn.net.cn

['a'].bspring-doc.cadn.net.cn

包含三个对象的数组spring-doc.cadn.net.cn

a.b[]spring-doc.cadn.net.cn

包含三个对象的数组spring-doc.cadn.net.cn

a.b[].cspring-doc.cadn.net.cn

一个包含字符串 onetwo 的数组spring-doc.cadn.net.cn

a.b[].dspring-doc.cadn.net.cn

字符串 threespring-doc.cadn.net.cn

a['e.dot']spring-doc.cadn.net.cn

字符串 fourspring-doc.cadn.net.cn

['a']['e.dot']spring-doc.cadn.net.cn

字符串 fourspring-doc.cadn.net.cn

您还可以记录一个根节点为数组的负载。 路径 [] 指代整个数组。 随后,您可以使用方括号或点号表示法来标识数组条目内的字段。 例如,[].id 对应于以下数组中每个对象所包含的 id 字段:spring-doc.cadn.net.cn

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

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

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

当对字段进行文档化时,Spring REST Docs 会尝试通过检查负载来确定其类型。 支持七种不同的类型:spring-doc.cadn.net.cn

类型 描述

arrayspring-doc.cadn.net.cn

每个字段出现的值都是一个数组。spring-doc.cadn.net.cn

booleanspring-doc.cadn.net.cn

每个字段出现的值是一个布尔值(truefalse)。spring-doc.cadn.net.cn

objectspring-doc.cadn.net.cn

每个字段出现的值都是一个对象。spring-doc.cadn.net.cn

numberspring-doc.cadn.net.cn

每个字段出现的值都是一个数字。spring-doc.cadn.net.cn

nullspring-doc.cadn.net.cn

每个字段出现的值均为 nullspring-doc.cadn.net.cn

stringspring-doc.cadn.net.cn

每个字段出现的值都是一个字符串。spring-doc.cadn.net.cn

variesspring-doc.cadn.net.cn

该字段在负载中多次出现,具有多种不同的类型。spring-doc.cadn.net.cn

您也可以通过在 FieldDescriptor 上使用 type(Object) 方法来显式设置类型。 所提供的 ObjecttoString 方法的结果将用于文档中。 通常,会使用由 JsonFieldType 枚举出的值之一。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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
REST Assured
.filter(document("user", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
	.description("The user's email address"))))
1 将字段的类型设置为 String
XML 负载

本节介绍如何使用 XML 负载。spring-doc.cadn.net.cn

XML 字段路径

XML 字段路径使用 XPath 进行描述。 / 用于进入子节点。spring-doc.cadn.net.cn

XML 字段类型

在记录 XML 负载时,您必须通过使用 FieldDescriptor 上的 type(Object) 方法来为字段提供类型。 所提供的类型的 toString 方法的结果将用于文档中。spring-doc.cadn.net.cn

复用字段描述符

除了对重用代码片段的通用支持外,请求和响应代码片段还允许使用路径前缀配置额外的描述符。 这使得只需创建一次重复的请求或响应负载部分的描述符,即可反复重用。spring-doc.cadn.net.cn

考虑一个返回书籍的端点:spring-doc.cadn.net.cn

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

titleauthor 的路径分别是 titleauthorspring-doc.cadn.net.cn

现在考虑一个返回书籍数组的端点:spring-doc.cadn.net.cn

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

titleauthor 的路径分别是 [].title[].author。 单本书籍与书籍数组之间的唯一区别在于,字段路径现在带有 []. 前缀。spring-doc.cadn.net.cn

您可以按以下方式创建描述书籍的描述符:spring-doc.cadn.net.cn

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

然后,您可以使用它们来记录单本书籍,如下所示:spring-doc.cadn.net.cn

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
REST Assured
RestAssured.given(this.spec)
	.accept("application/json")
	.filter(document("book", responseFields(book))) (1)
	.when()
	.get("/books/1")
	.then()
	.assertThat()
	.statusCode(is(200));
1 使用现有的描述符文档化 titleauthor

您还可以使用描述符来记录书籍数组,如下所示:spring-doc.cadn.net.cn

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
REST Assured
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 允许您通过提取负载的子部分并对其进行文档化来实现这一点。spring-doc.cadn.net.cn

记录请求或响应主体的子部分

考虑以下 JSON 响应体:spring-doc.cadn.net.cn

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

您可以生成一个代码片段,用于记录 temperature 对象,如下所示:spring-doc.cadn.net.cn

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
REST Assured
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 生成包含响应正文子部分的代码片段。 在 org.springframework.restdocs.payload.PayloadDocumentation 上使用静态的 responseBodybeneathPath 方法。 若要为请求正文生成代码片段,可以使用 requestBody 替代 responseBody

结果是一个包含以下内容的代码片段:spring-doc.cadn.net.cn

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

为了使代码片段的名称具有区分度,包含了子部分的标识符。 默认情况下,此标识符为 beneath-${path}。 例如,上述代码生成的代码片段名为 response-body-beneath-weather.temperature.adoc。 您可以使用 withSubsectionId(String) 方法自定义该标识符,如下所示:spring-doc.cadn.net.cn

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

结果是一个名为 request-body-temp.adoc 的代码片段。spring-doc.cadn.net.cn

记录请求或响应子部分的字段

除了记录请求或响应主体的某个子部分外,您还可以记录特定子部分中的字段。 您可以生成一个代码片段,用于记录 temperature 对象(highlow)的字段,如下所示:spring-doc.cadn.net.cn

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 下方子部分中的字段。 使用 org.springframework.restdocs.payload.PayloadDocumentation 上的静态 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 下方子部分中的字段。 使用 org.springframework.restdocs.payload.PayloadDocumentation 上的静态 beneathPath 方法。
2 记录 highlow 字段。
REST Assured
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 下方的子部分中的字段。使用 org.springframework.restdocs.payload.PayloadDocumentation 上的静态 beneathPath 方法。
2 记录 highlow 字段。

结果是一个代码片段,其中包含一个表格,用于描述 weather.temperaturehighlow 字段。 为了使该代码片段的名称具有唯一性,会包含一个子章节标识符。 默认情况下,此标识符为 beneath-${path}。 例如,上述代码将生成一个名为 response-fields-beneath-weather.temperature.adoc 的代码片段。spring-doc.cadn.net.cn

查询参数

您可以使用 queryParameters 来记录请求的查询参数。 以下示例展示了如何实现:spring-doc.cadn.net.cn

MockMvc
this.mockMvc.perform(get("/users?page=2&per_page=100")) (1)
	.andExpect(status().isOk())
	.andDo(document("users", queryParameters((2)
			parameterWithName("page").description("The page to retrieve"), (3)
			parameterWithName("per_page").description("Entries per page") (4)
	)));
1 使用两个参数 pageper_page 执行一个 GET 请求,并将它们放在查询字符串中。 查询参数应包含在 URL 中(如此处所示),或使用请求构建器的 queryParamqueryParams 方法指定。 应避免使用 paramparams 方法,因为这会导致参数来源不明确。
2 配置 Spring REST Docs 以生成描述请求查询参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 queryParameters 方法。
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", queryParameters((2)
			parameterWithName("page").description("The page to retrieve"), (3)
			parameterWithName("per_page").description("Entries per page") (4)
	)));
1 使用两个参数 pageper_page 在查询字符串中执行一个 GET 请求。
2 配置 Spring REST Docs 以生成描述请求查询参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 queryParameters 方法。
3 记录 page 参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
4 记录 per_page 参数。
REST Assured
RestAssured.given(this.spec)
	.filter(document("users", queryParameters((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 上的静态 queryParameters 方法。
2 记录 page 参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
3 记录 per_page 参数。
4 使用两个参数 pageper_page 在查询字符串中执行一个 GET 请求。

在记录查询参数时,如果请求的查询字符串中使用了未记录的查询参数,测试将失败。 同样,如果请求的查询字符串中找不到已记录的查询参数,且该参数未被标记为可选,测试也会失败。spring-doc.cadn.net.cn

如果您不想记录某个查询参数,可以将其标记为忽略。 这样可以防止它出现在生成的代码片段中,同时避免上述失败情况。spring-doc.cadn.net.cn

您还可以以宽松模式记录查询参数,其中任何未记录的参数都不会导致测试失败。 为此,请在 org.springframework.restdocs.request.RequestDocumentation 上使用 relaxedQueryParameters 方法。 这在记录特定场景时非常有用,您只需关注查询参数的子集。spring-doc.cadn.net.cn

表单参数

您可以使用 formParameters 来记录请求的表单参数。 以下示例展示了如何实现:spring-doc.cadn.net.cn

MockMvc
this.mockMvc.perform(post("/users").param("username", "Tester")) (1)
	.andExpect(status().isCreated())
	.andDo(document("create-user", formParameters((2)
			parameterWithName("username").description("The user's username") (3)
	)));
1 使用单个表单参数 username 执行一个 POST 请求。
2 配置 Spring REST Docs 以生成描述请求表单参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 formParameters 方法。
3 记录 username 参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
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", formParameters((2)
				parameterWithName("username").description("The user's username") (3)
		)));
1 使用单个表单参数 username 执行一个 POST 请求。
2 配置 Spring REST Docs 以生成描述请求表单参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 formParameters 方法。
3 记录 username 参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
REST Assured
RestAssured.given(this.spec)
	.filter(document("create-user", formParameters((1)
			parameterWithName("username").description("The user's username")))) (2)
	.formParam("username", "Tester")
	.when()
	.post("/users") (3)
	.then()
	.assertThat()
	.statusCode(is(200));
1 配置 Spring REST Docs 以生成描述请求表单参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 formParameters 方法。
2 记录 username 参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
3 使用单个表单参数 username 执行一个 POST 请求。

在所有情况下,结果都是一个名为 form-parameters.adoc 的代码片段,其中包含一个表格,用于描述该资源支持的表单参数。spring-doc.cadn.net.cn

在记录表单参数时,如果请求体中使用了未记录的表单参数,测试将失败。 同样地,如果请求体中找不到已记录的表单参数,且该表单参数未被标记为可选,测试也会失败。spring-doc.cadn.net.cn

如果您不想记录某个表单参数,可以将其标记为忽略。 这样可以防止它出现在生成的代码片段中,同时避免上述失败情况。spring-doc.cadn.net.cn

您还可以在宽松模式下记录表单参数,其中任何未记录的参数都不会导致测试失败。 为此,请在 org.springframework.restdocs.request.RequestDocumentation 上使用 relaxedFormParameters 方法。 这在记录特定场景时非常有用,您只需关注表单参数的子集。spring-doc.cadn.net.cn

路径参数

你可以使用 pathParameters 来记录请求的路径参数。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 使用两个路径参数 latitudelongitude 执行 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 使用两个路径参数 latitudelongitude 执行 GET 请求。
2 配置 Spring REST Docs 以生成描述请求路径参数的代码片段。 使用 org.springframework.restdocs.request.RequestDocumentation 上的静态 pathParameters 方法。
3 记录名为 latitude 的参数。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 parameterWithName 方法。
4 记录名为 longitude 的参数。
REST Assured
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 使用两个路径参数 latitudelongitude 执行 GET 请求。

结果是一个名为 path-parameters.adoc 的代码片段,其中包含一个表格,描述了该资源支持的路径参数。spring-doc.cadn.net.cn

如果您在 Spring Framework 6.1 或更早版本中使用 MockMvc,为了使路径参数可用于文档生成,您必须使用 RestDocumentationRequestBuilders 上的方法之一来构建请求,而不是使用 MockMvcRequestBuilders

在记录路径参数时,如果请求中使用了未记录的路径参数,测试将失败。 同样地,如果请求中找不到已记录的路径参数,且该路径参数未被标记为可选,测试也会失败。spring-doc.cadn.net.cn

您还可以以宽松模式记录路径参数,其中任何未记录的参数都不会导致测试失败。 为此,请在 org.springframework.restdocs.request.RequestDocumentation 上使用 relaxedPathParameters 方法。 这在记录特定场景时非常有用,您只需关注路径参数的子集。spring-doc.cadn.net.cn

如果您不想记录路径参数,可以将其标记为忽略。 这样做可以防止它出现在生成的代码片段中,同时避免前面描述的错误。spring-doc.cadn.net.cn

请求部件

您可以使用 requestParts 来记录多部分请求的各个部分。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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 的部分。 在 org.springframework.restdocs.request.RequestDocumentation 上使用静态 partWithName 方法。
REST Assured
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 /upload 执行 POST 请求。

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

在记录请求部分时,如果请求中使用了未记录的部分,则测试将失败。 同样地,如果请求中未找到已记录的部分,且该部分未被标记为可选,则测试也会失败。spring-doc.cadn.net.cn

您还可以以宽松模式记录请求部分,其中任何未记录的部分都不会导致测试失败。 为此,请在 org.springframework.restdocs.request.RequestDocumentation 上使用 relaxedRequestParts 方法。 这在记录特定场景时非常有用,您只需关注请求部分的子集。spring-doc.cadn.net.cn

如果您不想记录某个请求部分,可以将其标记为忽略。 这样可以防止它出现在生成的代码片段中,同时避免之前描述的失败情况。spring-doc.cadn.net.cn

请求部分负载

您可以像记录请求的有效负载一样,记录请求部分的有效负载,支持记录请求部分的内容及其字段。spring-doc.cadn.net.cn

记录请求部分的主体

您可以按以下方式生成包含请求部分主体的代码片段:spring-doc.cadn.net.cn

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 的请求部分的正文。 使用 PayloadDocumentation 上的静态 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 方法。
REST Assured
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 的代码片段。spring-doc.cadn.net.cn

记录请求部分的字段

您可以按照与请求或响应字段类似的方式,记录请求部分的字段,如下所示:spring-doc.cadn.net.cn

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 的请求部分中负载的字段。 使用 PayloadDocumentation 上的静态 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 的请求部分中负载的字段。 使用 PayloadDocumentation 上的静态 requestPartFields 方法。
2 期望一个路径为 version 的字段。 在 org.springframework.restdocs.payload.PayloadDocumentation 上使用静态 fieldWithPath 方法。
REST Assured
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 的请求部分中负载的字段。 使用 PayloadDocumentation 上的静态 requestPartFields 方法。
2 期望一个路径为 version 的字段。 在 org.springframework.restdocs.payload.PayloadDocumentation 上使用静态 fieldWithPath 方法。

结果是一个包含描述部件字段表格的代码片段。 此代码片段名为 request-part-${part-name}-fields.adoc。 例如,记录一个名为 metadata 的部件会生成一个名为 request-part-metadata-fields.adoc 的代码片段。spring-doc.cadn.net.cn

在记录字段时,如果在部分的负载中发现未记录的字段,则测试将失败。 同样,如果记录的字段未在部分的负载中找到,且该字段未被标记为可选,则测试也会失败。 对于具有层次结构的负载,记录一个字段足以使其所有后代也被视为已记录。spring-doc.cadn.net.cn

如果您不想记录某个字段,可以将其标记为忽略。 这样做可以防止它出现在生成的代码片段中,同时避免上述失败情况。spring-doc.cadn.net.cn

您还可以在宽松模式下记录字段,其中任何未记录的字段都不会导致测试失败。 为此,请在 org.springframework.restdocs.payload.PayloadDocumentation 上使用 relaxedRequestPartFields 方法。 这在记录特定场景时非常有用,您只需关注部分负载的子集。spring-doc.cadn.net.cn

有关描述字段、记录使用 XML 的有效负载等更多信息,请参阅关于记录请求和响应有效负载的部分spring-doc.cadn.net.cn

HTTP 头部

您可以分别使用 requestHeadersresponseHeaders 来记录请求或响应中的标头。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 的代码片段。 每个片段都包含一个描述请求头的表格。spring-doc.cadn.net.cn

在记录 HTTP 头信息时,如果在请求或响应中未找到已记录的头信息,则测试将失败。spring-doc.cadn.net.cn

HTTP Cookie

您可以分别使用 requestCookiesresponseCookies 来记录请求或响应中的 cookie。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

MockMvc
this.mockMvc.perform(get("/").cookie(new Cookie("JSESSIONID", "ACBCDFD0FF93D5BB"))) (1)
	.andExpect(status().isOk())
	.andDo(document("cookies", requestCookies((2)
			cookieWithName("JSESSIONID").description("Session token")), (3)
			responseCookies((4)
					cookieWithName("JSESSIONID").description("Updated session token"),
					cookieWithName("logged_in")
						.description("Set to true if the user is currently logged in"))));
1 使用 JSESSIONID cookie 发起 GET 请求。
2 配置 Spring REST Docs 以生成描述请求 Cookie 的代码片段。 使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 requestCookies 方法。
3 记录 JSESSIONID cookie。使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 cookieWithName 方法。
4 生成一个描述响应中 Cookie 的代码片段。 在 org.springframework.restdocs.cookies.CookieDocumentation 上使用静态 responseCookies 方法。
WebTestClient
this.webTestClient.get()
	.uri("/people")
	.cookie("JSESSIONID", "ACBCDFD0FF93D5BB=") (1)
	.exchange()
	.expectStatus()
	.isOk()
	.expectBody()
	.consumeWith(document("cookies", requestCookies((2)
			cookieWithName("JSESSIONID").description("Session token")), (3)
			responseCookies((4)
					cookieWithName("JSESSIONID").description("Updated session token"),
					cookieWithName("logged_in").description("User is logged in"))));
1 使用 JSESSIONID cookie 发起 GET 请求。
2 配置 Spring REST Docs 以生成描述请求 Cookie 的代码片段。 使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 requestCookies 方法。
3 记录 JSESSIONID cookie。 使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 cookieWithName 方法。
4 生成一个描述响应中 Cookie 的代码片段。 在 org.springframework.restdocs.cookies.CookieDocumentation 上使用静态 responseCookies 方法。
REST Assured
RestAssured.given(this.spec)
	.filter(document("cookies", requestCookies((1)
			cookieWithName("JSESSIONID").description("Saved session token")), (2)
			responseCookies((3)
					cookieWithName("logged_in").description("If user is logged in"),
					cookieWithName("JSESSIONID").description("Updated session token"))))
	.cookie("JSESSIONID", "ACBCDFD0FF93D5BB") (4)
	.when()
	.get("/people")
	.then()
	.assertThat()
	.statusCode(is(200));
1 配置 Spring REST Docs 以生成描述请求 Cookie 的代码片段。 使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 requestCookies 方法。
2 记录 JSESSIONID cookie。 使用 org.springframework.restdocs.cookies.CookieDocumentation 上的静态 cookieWithName 方法。
3 生成一个描述响应中 Cookie 的代码片段。 在 org.springframework.restdocs.cookies.CookieDocumentation 上使用静态 responseCookies 方法。
4 在请求中发送一个 JSESSIONID cookie。

结果是一个名为 request-cookies.adoc 的代码片段和一个名为 response-cookies.adoc 的代码片段。 每个片段都包含一个描述 Cookie 的表格。spring-doc.cadn.net.cn

在记录 HTTP Cookie 时,如果请求或响应中存在未记录的 Cookie,测试将失败。 同样,如果已记录的 Cookie 未被找到,且该 Cookie 未标记为可选,测试也会失败。 您还可以以宽松模式记录 Cookie,在此模式下,任何未记录的 Cookie 都不会导致测试失败。 为此,请在 org.springframework.restdocs.cookies.CookieDocumentation 上使用 relaxedRequestCookiesrelaxedResponseCookies 方法。 当您只想关注部分 Cookie 的特定场景时,此功能非常有用。 如果您不想记录某个 Cookie,可以将其标记为忽略。 这样做可防止其出现在生成的代码片段中,同时避免前述的测试失败情况。spring-doc.cadn.net.cn

复用代码片段

被文档化的 API 通常在其多个资源中具有某些通用功能。 为了避免在文档化此类资源时出现重复,您可以复用配置了通用元素的 Snippetspring-doc.cadn.net.cn

首先,创建描述通用元素的 Snippet。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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"));

其次,使用此代码片段并添加特定于资源的进一步描述符。 以下示例展示了如何操作:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

记录约束条件

Spring REST Docs 提供了多个类,可帮助您记录约束条件。 您可以使用 ConstraintDescriptions 的实例来访问类的约束描述。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 类展示了此功能的实际运行效果。spring-doc.cadn.net.cn

查找约束

默认情况下,约束是通过使用 Bean Validation Validator 来查找的。 目前,仅支持属性约束。 您可以通过创建带有自定义 ValidatorConstraintResolver 实例的 ConstraintDescriptions 来自定义所使用的 Validator。 若要完全控制约束解析,您可以使用自己的 ConstraintResolver 实现。spring-doc.cadn.net.cn

描述约束

Bean Validation 3.0 的所有约束都提供了默认描述:spring-doc.cadn.net.cn

以下来自 Hibernate Validator 的约束也提供了默认描述:spring-doc.cadn.net.cn

要覆盖默认描述或提供新描述,您可以创建一个基名为 org.springframework.restdocs.constraints.ConstraintDescriptions 的资源包。 基于 Spring HATEOAS 的示例包含此类资源包的示例spring-doc.cadn.net.cn

资源包中的每个键都是约束的完全限定名加上 .description。 例如,标准 @NotNull 约束的键是 jakarta.validation.constraints.NotNull.descriptionspring-doc.cadn.net.cn

您可以在约束的描述中使用属性占位符来引用该约束的属性。 例如,@Min 约束的默认描述 Must be at least ${value} 引用了该约束的 value 属性。spring-doc.cadn.net.cn

若要更灵活地控制约束描述解析,您可以使用自定义的 ResourceBundleConstraintDescriptionResolver 创建 ConstraintDescriptions。 若需完全掌控,您可以使用自定义的 ConstraintDescriptionResolver 实现创建 ConstraintDescriptionsspring-doc.cadn.net.cn

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

一旦您获得了约束的描述,您就可以在生成的代码片段中随意使用它们。 例如,您可能希望将约束描述作为字段描述的一部分包含在内。 或者,您也可以将约束作为请求字段代码片段中的额外信息包含进去。 基于 Spring HATEOAS 的示例中的 ApiDocumentation 类展示了后一种方法。spring-doc.cadn.net.cn

默认代码片段

当您记录请求和响应时,会自动生成许多代码片段。spring-doc.cadn.net.cn

代码片段 描述

curl-request.adocspring-doc.cadn.net.cn

包含与正在记录的 MockMvc 调用等效的 curl 命令。spring-doc.cadn.net.cn

httpie-request.adocspring-doc.cadn.net.cn

包含与正在记录的 MockMvc 调用等效的 HTTPie 命令。spring-doc.cadn.net.cn

http-request.adocspring-doc.cadn.net.cn

包含等同于正在记录的 MockMvc 调用的 HTTP 请求。spring-doc.cadn.net.cn

http-response.adocspring-doc.cadn.net.cn

包含已返回的 HTTP 响应。spring-doc.cadn.net.cn

request-body.adocspring-doc.cadn.net.cn

包含已发送请求的请求体。spring-doc.cadn.net.cn

response-body.adocspring-doc.cadn.net.cn

包含返回的响应主体。spring-doc.cadn.net.cn

您可以配置默认生成哪些代码片段。 请参阅 配置部分 以获取更多信息。spring-doc.cadn.net.cn

使用参数化输出目录

您可以对document使用的输出目录进行参数化。 支持以下参数:spring-doc.cadn.net.cn

参数 描述

{methodName}spring-doc.cadn.net.cn

测试方法的未修改名称。spring-doc.cadn.net.cn

{method-name}spring-doc.cadn.net.cn

测试方法的名称,使用短横线命名法(kebab-case)格式化。spring-doc.cadn.net.cn

{method_name}spring-doc.cadn.net.cn

测试方法的名称,使用蛇形命名法(snake_case)格式化。spring-doc.cadn.net.cn

{ClassName}spring-doc.cadn.net.cn

测试类的未修改的简单名称。spring-doc.cadn.net.cn

{class-name}spring-doc.cadn.net.cn

测试类的简单名称,使用短横线命名法(kebab-case)格式化。spring-doc.cadn.net.cn

{class_name}spring-doc.cadn.net.cn

测试类的简单名称,使用蛇形命名法(snake_case)格式化。spring-doc.cadn.net.cn

{step}spring-doc.cadn.net.cn

当前测试中对服务进行的调用次数。spring-doc.cadn.net.cn

例如,在名为 creatingANote 的测试类 GettingStartedDocumentation 中的测试方法里,document("{class-name}/{method-name}") 会将代码片段写入名为 getting-started-documentation/creating-a-note 的目录中。spring-doc.cadn.net.cn

参数化的输出目录在与 @Before 方法结合使用时特别有用。 它允许在设置方法中一次性配置文档,然后在类中的每个测试中重复使用。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

MockMvc
@Before
public void setUp() {
	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
		.apply(documentationConfiguration(this.restDocumentation))
		.alwaysDo(document("{method-name}/{step}/"))
		.build();
}
REST Assured
@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-doc.cadn.net.cn

自定义输出

本节介绍如何自定义 Spring REST Docs 的输出。spring-doc.cadn.net.cn

自定义生成的代码片段

Spring REST Docs 使用 Mustache 模板来生成代码片段。 针对 Spring REST Docs 能够生成的每种代码片段,都提供了默认模板。 若要自定义代码片段的内容,您可以提供自己的模板。spring-doc.cadn.net.cn

模板从类路径下的 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 的模板。spring-doc.cadn.net.cn

包含额外信息

有两种方式可以提供额外信息以包含在生成的代码片段中:spring-doc.cadn.net.cn

  • 在描述符上使用 attributes 方法以添加一个或多个属性。spring-doc.cadn.net.cn

  • 在调用 curlRequesthttpRequesthttpResponse 等时传入一些属性。 这些属性与整个代码片段相关联。spring-doc.cadn.net.cn

任何额外的属性都会在模板渲染过程中提供。 结合自定义片段模板,这使得在生成的片段中包含额外信息成为可能。spring-doc.cadn.net.cn

一个具体的例子是在记录请求字段时添加约束列和标题。 第一步是为每个记录的字段提供一个 constraints 属性,并提供一个 title 属性。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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 属性。
REST Assured
.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 的自定义模板,该模板在生成的代码片段的表格中包含有关字段约束的信息,并添加标题。spring-doc.cadn.net.cn

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

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

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