记录您的 API
本节提供了有关使用 Spring REST Docs 记录 API 的更多详细信息。
空安全
声明 API 的可空性的主要目标是防止NullPointerException
避免在运行时抛出。
这是通过 Java 和 Kotlin 提供的构建时检查来实现的。
使用 Java 执行检查需要一些工具,例如 NullAway 或支持 JSpecify 注释的 IDE,例如 IntelliJ IDEA。
Kotlin 会自动进行检查,Kotlin 将 JSpecify 注释转换为 Kotlin 的 null 安全性。
要了解有关 Spring 的 null 安全性的更多信息,请参阅 Spring 框架参考文档。
超媒体
Spring REST Docs 支持在基于超媒体的 API 中记录链接。 以下示例显示了如何使用它:
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 文档以生成描述响应链接的片段。
使用静态links 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | 期待一个链接,其rel 是alpha .
使用静态linkWithRel 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | 期待一个链接,其rel 是bravo . |
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 文档以生成描述响应链接的片段。
使用静态links 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | 期待一个链接,其rel 是alpha .
使用静态linkWithRel 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | 期待一个链接,其rel 是bravo . |
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 文档以生成描述响应链接的片段。
使用静态links 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
2 | 期待一个链接,其rel 是alpha .使用静态linkWithRel 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
3 | 期待一个链接,其rel 是bravo . |
结果是一个名为links.adoc
其中包含描述资源链接的表。
如果响应中的链接具有title ,您可以从其描述符中省略描述,并且title 被使用。
如果您省略了描述,并且链接没有title ,则发生故障。 |
记录链接时,如果在响应中找到未记录的链接,则测试失败。 同样,如果在响应中找不到记录的链接,并且该链接未标记为可选,则测试也会失败。
如果不想记录链接,可以将其标记为已忽略。 这样做可以防止它出现在生成的代码片段中,同时避免上述失败。
您还可以以宽松模式记录链接,其中任何未记录的链接都不会导致测试失败。
为此,请使用relaxedLinks
方法org.springframework.restdocs.hypermedia.HypermediaDocumentation
.
当记录您只想关注链接子集的特定场景时,这可能很有用。
超媒体链接格式
默认情况下,理解两种链接格式:
-
Atom:链接应位于名为
links
. 当响应的内容类型与application/json
. -
HAL:链接应位于名为
_links
. 当响应的内容类型与application/hal+json
.
如果您使用 Atom 或 HAL 格式的链接但内容类型不同,则可以提供内置的LinkExtractor
实现到links
.
以下示例显示了如何执行此作:
.andDo(document("index", links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | 指示链接采用 HAL 格式。
使用静态halLinks 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
.consumeWith(document("index", links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))));
1 | 指示链接采用 HAL 格式。
使用静态halLinks 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
.filter(document("index", links(halLinks(), (1)
linkWithRel("alpha").description("Link to the alpha resource"),
linkWithRel("bravo").description("Link to the bravo resource"))))
1 | 指示链接采用 HAL 格式。使用静态halLinks 方法org.springframework.restdocs.hypermedia.HypermediaDocumentation . |
如果您的 API 以 Atom 或 HAL 以外的格式表示其链接,则可以提供自己的LinkExtractor
接口以从响应中提取链接。
忽略常见链接
而不是记录每个响应通用的链接,例如self
和curies
使用 HAL 时,您可能希望在概述部分记录一次,然后在 API 文档的其余部分忽略它们。
为此,您可以在对重复使用代码段的支持的基础上构建,将链接描述符添加到预配置为忽略某些链接的代码段。
以下示例显示了如何执行此作:
public static LinksSnippet links(LinkDescriptor... descriptors) {
return HypermediaDocumentation.links(linkWithRel("self").ignored().optional(), linkWithRel("curies").ignored())
.and(descriptors);
}
请求和响应有效负载
除了前面描述的特定于超媒体的支持外,还提供了对请求和响应有效负载的常规文档的支持。
默认情况下,Spring REST Docs 会自动为请求正文和响应正文生成代码段。
这些片段被命名为request-body.adoc
和response-body.adoc
分别。
请求和响应字段
为了提供请求或响应有效负载的更详细文档,提供了对记录有效负载字段的支持。
请考虑以下有效负载:
{
"contact": {
"name": "Jane Doe",
"email": "[email protected]"
}
}
您可以按如下方式记录上一个示例的字段:
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 文档以生成描述响应有效负载中字段的片段。
要记录请求,您可以使用requestFields .
两者都是静态方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 期望一个路径为contact.email .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
3 | 期望一个路径为contact.name . |
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 文档以生成描述响应有效负载中字段的片段。
要记录请求,您可以使用requestFields .
两者都是静态方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 期望一个路径为contact.email .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
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 文档以生成描述响应有效负载中字段的片段。
要记录请求,您可以使用requestFields .
两者都是静态方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 期望一个路径为contact.email .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
3 | 期望一个路径为contact.name . |
结果是一个代码片段,其中包含描述字段的表。对于请求,此代码段名为request-fields.adoc
. 对于响应,此代码段名为response-fields.adoc
.
记录字段时,如果在有效负载中找到未记录的字段,则测试失败。同样,如果在有效负载中找不到记录的字段,并且该字段未标记为可选,则测试也会失败。
如果您不想为所有字段提供详细的文档,可以记录有效负载的整个子部分。以下示例显示了如何执行此作:
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.email 和contact.name 现在也被视为已被记录在案。
使用静态subsectionWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
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.email 和contact.name 现在也被视为已被记录在案。
使用静态subsectionWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
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.email 和contact.name 现在也被视为已被记录在案。
使用静态subsectionWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
subsectionWithPath
可用于提供有效负载特定部分的高级概述。
然后,您可以为小节生成单独的、更详细的文档。
请参阅记录请求或响应有效负载的子部分。
如果您根本不想记录字段或子部分,则可以将其标记为忽略。这可以防止它出现在生成的代码片段中,同时避免前面描述的失败。
您还可以以宽松模式记录字段,其中任何未记录的字段都不会导致测试失败。为此,请使用relaxedRequestFields
和relaxedResponseFields
方法org.springframework.restdocs.payload.PayloadDocumentation
. 当记录只想关注有效负载子集的特定场景时,这可能很有用。
默认情况下,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 有效负载中,以下路径都存在:
路径 | 值 |
---|---|
|
包含 |
|
包含三个对象的数组 |
|
包含三个对象的数组 |
|
包含三个对象的数组 |
|
包含三个对象的数组 |
|
包含三个对象的数组 |
|
包含字符串的数组 |
|
字符串 |
|
字符串 |
|
字符串 |
您还可以记录在其根目录中使用数组的有效负载。
路径引用整个数组。
然后,您可以使用括号或点表示法来标识数组条目中的字段。
例如[]
[].id
对应于id
字段:
[
{
"id":1
},
{
"id":2
}
]
您可以用作通配符来匹配具有不同名称的字段。 例如*
users.*.role
可用于记录以下 JSON 中每个用户的角色:
{
"users":{
"ab12cd34":{
"role": "Administrator"
},
"12ab34cd":{
"role": "Guest"
}
}
}
JSON 字段类型
记录字段时,Spring REST Docs 会尝试通过检查有效负载来确定其类型。支持七种不同的类型:
类型 | 描述 |
---|---|
|
字段的每个匹配值是一个数组。 |
|
该字段的每次出现的值是一个布尔值 ( |
|
字段的每个出现值都是一个对象。 |
|
字段的每次出现的值都是一个数字。 |
|
该字段的每次出现值为 |
|
字段的每个匹配值是一个字符串。 |
|
该字段在有效负载中多次出现,具有各种不同类型。 |
您还可以使用type(Object)
方法FieldDescriptor
. 结果toString
提供方法Object
在文档中使用。通常,由JsonFieldType
被使用。以下示例显示了如何执行此作:
.andDo(document("index", responseFields(fieldWithPath("contact.email").type(JsonFieldType.STRING) (1)
.description("The user's email address"))));
1 | 将字段的类型设置为String . |
.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 字段路径
XML 字段路径使用 XPath 进行描述。 用于下降到子节点。/
XML 字段类型
记录 XML 有效负载时,必须使用type(Object)
方法FieldDescriptor
.
提供的类型的toString
方法。
重用字段描述符
除了对重用代码片段的一般支持外,请求和响应代码段还允许使用路径前缀配置其他描述符。 这样,请求或响应有效负载的重复部分的描述符可以创建一次,然后重复使用。
考虑返回一本书的端点:
{
"title": "Pride and Prejudice",
"author": "Jane Austen"
}
路径title
和author
是title
和author
分别。
现在考虑一个返回书籍数组的端点:
[{
"title": "Pride and Prejudice",
"author": "Jane Austen"
},
{
"title": "To Kill a Mockingbird",
"author": "Harper Lee"
}]
路径title
和author
是[].title
和[].author
分别。
单册和册数组之间的唯一区别是字段的路径现在有一个[].
前缀。
您可以按如下方式创建记录书籍的描述符:
FieldDescriptor[] book = new FieldDescriptor[] { fieldWithPath("title").description("Title of the book"),
fieldWithPath("author").description("Author of the book") };
然后,您可以使用它们来记录单本书,如下所示:
this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book", responseFields(book))); (1)
1 | 公文title 和author 通过使用现有描述符 |
this.webTestClient.get().uri("/books/1").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("book",
responseFields(book))); (1)
1 | 公文title 和author 通过使用现有描述符 |
RestAssured.given(this.spec)
.accept("application/json")
.filter(document("book", responseFields(book))) (1)
.when()
.get("/books/1")
.then()
.assertThat()
.statusCode(is(200));
1 | 公文title 和author 通过使用现有描述符 |
您还可以使用描述符来记录书籍数组,如下所示:
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 通过使用以[]. |
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
对象如下:
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("location", responseBody(beneathPath("weather.temperature")))); (1)
1 | 生成包含响应正文小节的代码片段。
使用静态responseBody 和beneathPath 方法org.springframework.restdocs.payload.PayloadDocumentation .
要为请求正文生成代码片段,您可以使用requestBody 代替responseBody . |
this.webTestClient.get().uri("/locations/1").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("temperature",
responseBody(beneathPath("weather.temperature")))); (1)
1 | 生成包含响应正文小节的代码片段。
使用静态responseBody 和beneathPath 方法org.springframework.restdocs.payload.PayloadDocumentation .
要为请求正文生成代码片段,您可以使用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 | 生成包含响应正文小节的代码片段。
使用静态responseBody 和beneathPath 方法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
对象 (high
和low
) 如下所示:
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 方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 记录high 和low 领域。 |
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 方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 记录high 和low 领域。 |
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 方法org.springframework.restdocs.payload.PayloadDocumentation . |
2 | 记录high 和low 领域。 |
结果是一个代码片段,其中包含一个表,该表描述了high
和low
字段weather.temperature
.
为了使代码段的名称不同,包含该小节的标识符。
默认情况下,此标识符为beneath-${path}
.
例如,前面的代码会生成一个名为response-fields-beneath-weather.temperature.adoc
.
查询参数
您可以使用以下命令记录请求的查询参数queryParameters
.
以下示例显示了如何执行此作:
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 | 执行GET 请求,其中包含两个参数,page 和per_page ,在查询字符串中。
查询参数应包含在 URL 中,如下所示,或使用请求构建器的queryParam 或queryParams 方法。
这param 和params 应避免使用方法,因为参数的来源是模棱两可的。 |
2 | 配置 Spring REST Docs 以生成描述请求查询参数的片段。
使用静态queryParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录page query 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
4 | 记录per_page query 参数。 |
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 | 执行GET 请求,其中包含两个参数,page 和per_page ,在查询字符串中。 |
2 | 配置 Spring REST Docs 以生成描述请求查询参数的片段。
使用静态queryParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录page 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
4 | 记录per_page 参数。 |
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 以生成描述请求查询参数的片段。
使用静态queryParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
2 | 记录page 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录per_page 参数。 |
4 | 执行GET 请求,其中包含两个参数,page 和per_page ,在查询字符串中。 |
记录查询参数时,如果在请求的查询字符串中使用了未记录的查询参数,则测试将失败。 同样,如果在请求的查询字符串中找不到记录的查询参数,并且该参数尚未标记为可选,则测试也会失败。
如果您不想记录查询参数,可以将其标记为忽略。这可以防止它出现在生成的代码片段中,同时避免上述失败。
您还可以在宽松模式下记录查询参数,其中任何未记录的参数都不会导致测试失败。为此,请使用relaxedQueryParameters
方法org.springframework.restdocs.request.RequestDocumentation
. 当记录只想关注查询参数子集的特定方案时,这可能很有用。
表单参数
您可以使用以下命令记录请求的表单参数formParameters
.
以下示例显示了如何执行此作:
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 | 执行POST 请求,单个表单参数,username . |
2 | 配置 Spring REST Docs 以生成描述请求表单参数的片段。
使用静态formParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录username 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
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 | 执行POST 请求,单个表单参数,username . |
2 | 配置 Spring REST Docs 以生成描述请求表单参数的片段。
使用静态formParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录username 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
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 以生成描述请求表单参数的片段。
使用静态formParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
2 | 记录username 参数。
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 执行POST 请求,单个表单参数,username . |
在所有情况下,结果都是一个名为form-parameters.adoc
其中包含描述资源支持的表单参数的表。
记录表单参数时,如果在请求正文中使用了未记录的表单参数,则测试将失败。 同样,如果在请求正文中找不到记录的表单参数,并且表单参数未标记为可选,则测试也会失败。
如果不想记录表单参数,可以将其标记为忽略。 这可以防止它出现在生成的代码片段中,同时避免上述故障。
您还可以在宽松模式下记录表单参数,其中任何未记录的参数都不会导致测试失败。
为此,请使用relaxedFormParameters
方法org.springframework.restdocs.request.RequestDocumentation
.
当记录您只想关注表单参数子集的特定场景时,这可能很有用。
路径参数
您可以使用以下命令记录请求的路径参数pathParameters
.
以下示例显示了如何执行此作:
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 | 执行GET 请求具有两个路径参数,latitude 和longitude . |
2 | 配置 Spring REST Docs 以生成描述请求路径参数的代码片段。
使用静态pathParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录名为latitude .
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
4 | 记录名为longitude . |
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 请求具有两个路径参数,latitude 和longitude . |
2 | 配置 Spring REST Docs 以生成描述请求路径参数的代码片段。
使用静态pathParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录名为latitude .
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
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 以生成描述请求路径参数的代码片段。
使用静态pathParameters 方法org.springframework.restdocs.request.RequestDocumentation . |
2 | 记录名为latitude .
使用静态parameterWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录名为longitude . |
4 | 执行GET 请求具有两个路径参数,latitude 和longitude . |
结果是一个名为path-parameters.adoc
其中包含一个表,该表描述了资源支持的路径参数。
记录路径参数时,如果请求中使用了未记录的路径参数,则测试将失败。 同样,如果在请求中找不到记录的路径参数,并且路径参数未标记为可选,则测试也会失败。
您还可以在宽松模式下记录路径参数,其中任何未记录的参数都不会导致测试失败。为此,请使用relaxedPathParameters
方法org.springframework.restdocs.request.RequestDocumentation
. 当记录只想关注路径参数子集的特定场景时,这非常有用。
如果不想记录路径参数,可以将其标记为忽略。这样做可以防止它出现在生成的代码片段中,同时避免前面描述的失败。
索取零件
您可以使用requestParts
以记录多部分请求的各个部分。以下示例显示了如何执行此作:
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 | 执行POST 请求,单个部分名为file . |
2 | 配置 Spring REST Docs 以生成描述请求部分的片段。使用静态requestParts 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录名为file .
使用静态partWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
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 | 执行POST 请求,单个部分名为file . |
2 | 配置 Spring REST Docs 以生成描述请求部分的片段。使用静态requestParts 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 记录名为file .
使用静态partWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
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 以生成描述请求部分的片段。使用静态requestParts 方法org.springframework.restdocs.request.RequestDocumentation . |
2 | 记录名为file .使用静态partWithName 方法org.springframework.restdocs.request.RequestDocumentation . |
3 | 使用名为file . |
4 | 执行POST 请求/upload . |
结果是一个名为request-parts.adoc
其中包含描述资源支持的请求部分的表。
记录请求部件时,如果请求中使用了未记录的部件,则测试将失败。 同样,如果在请求中找不到记录的部件,并且该部件未标记为可选部件,则测试也会失败。
您还可以以宽松模式记录请求部件,其中任何未记录的部件都不会导致测试失败。
为此,请使用relaxedRequestParts
方法org.springframework.restdocs.request.RequestDocumentation
.
当记录您只想关注请求部分的子集的特定场景时,这可能很有用。
如果不想记录请求部件,可以将其标记为已忽略。 这可以防止它出现在生成的代码片段中,同时避免前面描述的故障。
请求部件有效负载
您可以像记录请求部分的有效负载一样记录请求部分的有效负载,并支持记录请求部分的正文及其字段。
记录请求部件的正文
您可以生成包含请求部分正文的代码片段,如下所示:
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 文档以生成一个代码片段,其中包含名为metadata .
使用静态requestPartBody 方法PayloadDocumentation . |
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 文档以生成一个代码片段,其中包含名为metadata .
使用静态requestPartBody 方法PayloadDocumentation . |
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 文档以生成一个代码片段,其中包含名为metadata .
使用静态requestPartBody 方法PayloadDocumentation . |
结果是一个名为request-part-${part-name}-body.adoc
包含零件的主体。
例如,记录名为metadata
生成一个名为request-part-metadata-body.adoc
.
记录请求部件的字段
您可以像记录请求或响应的字段一样记录请求部件的字段,如下所示:
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 文档以生成一个片段,描述名为metadata .
使用静态requestPartFields 方法PayloadDocumentation . |
2 | 期望一个路径为version .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
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 文档以生成一个片段,描述名为metadata .
使用静态requestPartFields 方法PayloadDocumentation . |
2 | 期望一个路径为version .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
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 文档以生成一个片段,描述名为metadata .
使用静态requestPartFields 方法PayloadDocumentation . |
2 | 期望一个路径为version .
使用静态fieldWithPath 方法org.springframework.restdocs.payload.PayloadDocumentation . |
结果是一个代码片段,其中包含描述部件字段的表。
此代码段名为request-part-${part-name}-fields.adoc
.
例如,记录名为metadata
生成一个名为request-part-metadata-fields.adoc
.
记录字段时,如果在部件的有效负载中找到未记录的字段,则测试将失败。 同样,如果在部件的有效负载中找不到记录的字段,并且该字段未标记为可选,则测试也会失败。 对于具有分层结构的有效负载,记录一个字段足以使其所有后代也被视为已记录。
如果不想记录字段,可以将其标记为忽略。 这样做可以防止它出现在生成的代码片段中,同时避免上述失败。
您还可以以宽松模式记录字段,其中任何未记录的字段都不会导致测试失败。为此,请使用relaxedRequestPartFields
方法org.springframework.restdocs.payload.PayloadDocumentation
.
当记录您只想关注部件有效负载子集的特定场景时,这可能很有用。
有关描述字段、记录使用 XML 的有效负载等的更多信息,请参阅有关记录请求和响应有效负载的部分。
HTTP 标头
您可以使用以下命令记录请求或响应中的标头requestHeaders
和responseHeaders
分别。
以下示例显示了如何执行此作:
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 | 执行GET 请求与Authorization 使用基本身份验证的标头。 |
2 | 配置 Spring REST Docs 以生成描述请求标头的片段。
使用静态requestHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
3 | 记录Authorization 页眉。
使用静态headerWithName 方法org.springframework.restdocs.headers.HeaderDocumentation . |
4 | 生成描述响应标头的代码段。
使用静态responseHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
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 | 执行GET 请求与Authorization 使用基本身份验证的标头。 |
2 | 配置 Spring REST Docs 以生成描述请求标头的片段。
使用静态requestHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
3 | 记录Authorization 页眉。
使用静态headerWithName 方法org.springframework.restdocs.headers.HeaderDocumentation . |
4 | 生成描述响应标头的代码段。
使用静态responseHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
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 以生成描述请求标头的片段。
使用静态requestHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
2 | 记录Authorization 页眉。
使用静态headerWithName 'org.springframework.restdocs.headers.HeaderDocumentation 上的方法。 |
3 | 生成描述响应标头的代码段。
使用静态responseHeaders 方法org.springframework.restdocs.headers.HeaderDocumentation . |
4 | 使用Authorization 使用基本身份验证的标头。 |
结果是一个名为request-headers.adoc
以及一个名为response-headers.adoc
.
每个都包含一个描述标头的表。
记录 HTTP 标头时,如果在请求或响应中找不到记录的标头,则测试将失败。
HTTP Cookie
您可以使用以下命令在请求或响应中记录 cookierequestCookies
和responseCookies
分别。
以下示例显示了如何执行此作:
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 饼干。 |
2 | 配置 Spring REST Docs 以生成描述请求 cookie 的片段。
使用静态requestCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
3 | 记录JSESSIONID 饼干。使用静态cookieWithName 方法org.springframework.restdocs.cookies.CookieDocumentation . |
4 | 生成描述响应 cookie 的片段。
使用静态responseCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
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 饼干。 |
2 | 配置 Spring REST Docs 以生成描述请求 cookie 的片段。
使用静态requestCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
3 | 记录JSESSIONID 饼干。
使用静态cookieWithName 方法org.springframework.restdocs.cookies.CookieDocumentation . |
4 | 生成描述响应 cookie 的片段。
使用静态responseCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
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 的片段。
使用静态requestCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
2 | 记录JSESSIONID 饼干。
使用静态cookieWithName 方法org.springframework.restdocs.cookies.CookieDocumentation . |
3 | 生成描述响应 cookie 的片段。
使用静态responseCookies 方法org.springframework.restdocs.cookies.CookieDocumentation . |
4 | 发送JSESSIONID cookie 与请求。 |
结果是一个名为request-cookies.adoc
以及一个名为response-cookies.adoc
.
每个都包含一个描述 cookie 的表格。
记录 HTTP cookie 时,如果在请求或响应中发现未记录的 cookie,则测试将失败。
同样,如果未找到记录的 cookie 并且该 cookie 未标记为可选,则测试也会失败。
您还可以以宽松模式记录 cookie,其中任何未记录的 cookie 都不会导致测试失败。
为此,请使用relaxedRequestCookies
和relaxedResponseCookies
方法org.springframework.restdocs.cookies.CookieDocumentation
.
当记录您只想关注 cookie 子集的特定场景时,这可能很有用。
如果您不想记录 cookie,可以将其标记为已忽略。
这样做可以防止它出现在生成的代码片段中,同时避免前面描述的失败。
重用代码段
通常,正在记录的 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"));
其次,使用此代码片段并添加更多特定于资源的描述符。 以下示例显示了如何执行此作:
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 添加特定于正在记录的资源的描述符。 |
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 添加特定于正在记录的资源的描述符。 |
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
值first
,last
,next
,previous
,alpha
和bravo
都被记录在案。
记录约束
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 | 创建ConstraintDescriptions 对于UserInput 类。 |
2 | 获取name 属性的约束。
此列表包含两个描述:一个用于NotNull 约束,一个用于Size 约束。 |
这ApiDocumentation
Spring HATEOAS 示例中的类显示了此功能的实际效果。
查找约束
默认情况下,使用 Bean 验证来查找约束Validator
.
目前,仅支持属性约束。
您可以自定义Validator
通过创建ConstraintDescriptions
使用自定义ValidatorConstraintResolver
实例。
要完全控制约束解析,您可以使用自己的实现ConstraintResolver
.
描述约束
为 Bean Validation 3.1 的所有约束提供了默认描述:
-
AssertFalse
-
AssertTrue
-
DecimalMax
-
DecimalMin
-
Digits
-
Email
-
Future
-
FutureOrPresent
-
Max
-
Min
-
Negative
-
NegativeOrZero
-
NotBlank
-
NotEmpty
-
NotNull
-
Null
-
Past
-
PastOrPresent
-
Pattern
-
Positive
-
PositiveOrZero
-
Size
还为 Hibernate 中的以下约束提供了默认描述 验证者:
-
CodePointLength
-
CreditCardNumber
-
Currency
-
EAN
-
Email
-
Length
-
LuhnCheck
-
Mod10Check
-
Mod11Check
-
NotBlank
-
NotEmpty
-
Currency
-
Range
-
SafeHtml
-
URL
要覆盖默认描述或提供新描述,您可以创建一个基本名称为org.springframework.restdocs.constraints.ConstraintDescriptions
.
基于 Spring HATEOAS 的示例包含此类资源包的示例。
资源包中的每个键都是约束的完全限定名称加上.description
.
例如,标准的键@NotNull
constraint 是jakarta.validation.constraints.NotNull.description
.
您可以使用属性占位符在其描述中引用约束的属性。
例如,默认描述@Min
约束Must be at least ${value}
,引用约束的value
属性。
要更好地控制约束描述分辨率,可以创建ConstraintDescriptions
使用自定义ResourceBundleConstraintDescriptionResolver
.
要获得完全控制权,您可以创建ConstraintDescriptions
使用自定义ConstraintDescriptionResolver
实现。
在生成的代码片段中使用约束描述
一旦你有了约束的描述,你就可以在生成的片段中随意使用它们。
例如,您可能希望将约束描述作为字段描述的一部分。
或者,您可以将约束作为额外信息包含在请求字段代码段中。
这ApiDocumentation
class 说明了后一种方法。
默认片段
当您记录请求和响应时,会自动生成许多代码片段。
片段 | 描述 |
---|---|
|
包含 |
|
包含 |
|
包含等效于 |
|
包含返回的 HTTP 响应。 |
|
包含已发送请求的正文。 |
|
包含返回的响应的正文。 |
您可以配置默认生成的代码片段。 有关详细信息,请参阅配置部分。
使用参数化输出目录
您可以参数化document
.
支持以下参数:
参数 | 描述 |
---|---|
{方法名称} |
测试方法的未修改名称。 |
{方法名称} |
测试方法的名称,使用 kebab-case 格式化。 |
{method_name} |
测试方法的名称,使用 snake_case 进行格式化。 |
{类名} |
测试类的未修改简单名称。 |
{类名} |
测试类的简单名称,使用 kebab-case 格式化。 |
{class_name} |
测试类的简单名称,使用 snake_case 格式化。 |
{步骤} |
当前测试中对服务的调用计数。 |
例如document("{class-name}/{method-name}")
在名为creatingANote
在测试类上GettingStartedDocumentation
将代码片段写入名为getting-started-documentation/creating-a-note
.
参数化输出目录与@Before
方法。
它允许在设置方法中配置一次文档,然后在类中的每个测试中重复使用。
以下示例显示了如何执行此作:
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}/{step}/"))
.build();
}
@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
this.spec = new RequestSpecBuilder().addFilter(documentationConfiguration(restDocumentation))
.addFilter(document("{method-name}/{step}"))
.build();
}
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
this.webTestClient = WebTestClient.bindToApplicationContext(this.context)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.entityExchangeResultConsumer(document("{method-name}/{step}"))
.build();
}
使用此配置后,对您正在测试的服务的每次调用都会生成默认代码片段,而无需任何进一步的配置。看看GettingStartedDocumentation
类,以查看此功能的实际效果。
自定义输出
本节介绍如何自定义 Spring REST Docs 的输出。
自定义生成的片段
Spring REST Docs 使用 Mustache 模板来生成生成的片段。为 Spring REST Docs 可以生成的每个代码片段提供了默认模板。 要自定义代码段的内容,您可以提供自己的模板。
模板是从类路径从org.springframework.restdocs.templates
子包。
子包的名称由正在使用的模板格式的 ID 决定。
默认模板格式 Asciidoctor 的 ID 为asciidoctor
,因此代码段从org.springframework.restdocs.templates.asciidoctor
.
每个模板都以其生成的代码片段命名。
例如,要覆盖curl-request.adoc
snippet,创建一个名为curl-request.snippet
在src/test/resources/org/springframework/restdocs/templates/asciidoctor
.
包括额外信息
有两种方法可以提供额外信息以包含在生成的代码段中:
-
使用
attributes
方法向其添加一个或多个属性。 -
调用时传入一些属性
curlRequest
,httpRequest
,httpResponse
,依此类推。 此类属性与整个代码段相关联。
任何其他属性在模板渲染过程中都可用。 与自定义代码段模板相结合,可以在生成的代码段中包含额外的信息。
一个具体的例子是在记录请求字段时添加约束列和标题。
第一步是提供一个constraints
属性,并提供title
属性。
以下示例显示了如何执行此作:
.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 | 将constraints 属性的name 田。 |
3 | 将constraints 属性的email 田。 |
.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 | 将constraints 属性的name 田。 |
3 | 将constraints 属性的email 田。 |
.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 | 将constraints 属性的name 田。 |
3 | 将constraints 属性的email 田。 |
第二步是提供名为request-fields.snippet
其中包括有关生成的代码片段表中的字段约束的信息,并添加标题。
.{{title}} (1)
|===
|Path|Type|Description|Constraints (2)
{{#fields}}
|{{path}}
|{{type}}
|{{description}}
|{{constraints}} (3)
{{/fields}}
|===
1 | 向表格添加标题。 |
2 | 添加名为“约束”的新列。 |
3 | 包括描述符的constraints 属性。 |