From 52a90812c9d09f1b1a52c0d4c09faedc7b99225b Mon Sep 17 00:00:00 2001 From: ZhangKai Date: Tue, 8 Mar 2022 17:49:43 +0800 Subject: [PATCH] =?UTF-8?q?#26=20spring=20rest=20docs=20=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=AE=A1=E6=A0=B8=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vuepress/config.js | 11 + docs/spring-rest-docs/README.md | 1 + docs/spring-rest-docs/spring-restdocs.md | 2443 ++++++++++++++++++++++ 3 files changed, 2455 insertions(+) create mode 100644 docs/spring-rest-docs/README.md create mode 100644 docs/spring-rest-docs/spring-restdocs.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 935c029..a83e0d4 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -521,6 +521,17 @@ module.exports = { initialOpenGroupIndex: 0 // 可选的, 默认值是 0 } ], + '/spring-rest-docs/': [ + { + title: 'Spring HATEOAS', + sidebarDepth: 2, + collapsable: false, + children: [ + "/spring-rest-docs/spring-restdocs.md", + ], + initialOpenGroupIndex: 0 // 可选的, 默认值是 0 + } + ], // fallback '/': [{ diff --git a/docs/spring-rest-docs/README.md b/docs/spring-rest-docs/README.md new file mode 100644 index 0000000..189f235 --- /dev/null +++ b/docs/spring-rest-docs/README.md @@ -0,0 +1 @@ +# Spring REST Docs \ No newline at end of file diff --git a/docs/spring-rest-docs/spring-restdocs.md b/docs/spring-rest-docs/spring-restdocs.md new file mode 100644 index 0000000..dbbe9c4 --- /dev/null +++ b/docs/spring-rest-docs/spring-restdocs.md @@ -0,0 +1,2443 @@ +# Spring REST Docs + +将手写文档与通过 Spring MVC 测试、WebTestClient 或 Rest Assured 生成的自动生成的片段结合在一起来实现文档服务。 + +## [导言](#introduction) + +Spring REST DOCS 的目的是帮助你为 RESTful 服务生成准确且可读的文档。 + +编写高质量的文档是困难的。减轻这一困难的一种方法是使用非常适合这项工作的工具。为此, Spring REST DOCS 默认使用[ASCIIDoctor](https://asciidoctor.org)。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,而不是它的实现。它还可以帮助你改进实现,而无需重新编写文档。 + +## [开始](#getting-started) + +本节描述了如何开始使用 Spring REST DOCS。 + +### [示例应用程序](#getting-started-sample-applications) + +如果你想直接进入,可以使用一些示例应用程序: + +| 样本 |Build system|说明| +|------------------------------------------------------------------------------------------------------------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|[Spring Data REST](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-spring-data-rest)| Maven |演示如何为使用[Spring Data REST](https://projects.spring.io/spring-data-rest/)实现的服务创建入门指南和 API 指南。| +| [Spring HATEOAS](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-spring-hateoas) | Gradle |演示如何为使用[Spring HATEOAS](https://projects.spring.io/spring-hateoas/)实现的服务创建入门指南和 API 指南。| + +| 样本 |Build system| Description | +|---------------------------------------------------------------------------------------------------------------|------------|-----------------------------------------------------------------------------| +|[WebTestClient](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/web-test-client)| Gradle |Demonstrates the use of Spring REST docs with Spring WebFlux’s WebTestClient.| + +| 样本 |Build system| 说明 | +|-----------------------------------------------------------------------------------------------------------|------------|-------------------------------------------------------------------------------------| +|[放心吧](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-assured)| Gradle |Demonstrates the use of Spring REST Docs with [放心吧](http://rest-assured.io).| + +| Sample |Build system| Description | +|--------------------------------------------------------------------------------------------------------|------------|---------------------------------------------------------------------------------------------------| +|[Slate](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-slate)| Gradle |Demonstrates the use of Spring REST Docs with Markdown and[Slate](https://github.com/tripit/slate).| +|[TestNG](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/testng)| Gradle | Demonstrates the use of Spring REST Docs with [TestNG](http://testng.org). | +|[JUnit 5](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/junit5)| Gradle | Demonstrates the use of Spring REST Docs with [JUnit 5](https://junit.org/junit5/). | + +### [所需经费](#getting-started-requirements) + +Spring REST DOCS 具有以下最低要求: + +* Java8 + +* Spring 框架 5(5.0.2 或更高版本) + +此外,`spring-restdocs-restassured`模块需要 REST SUARD3.0。 + +### [构建配置](#getting-started-build-configuration) + +使用 Spring REST DOCS 的第一步是配置项目的构建。[Spring HATEOAS](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-spring-hateoas)和[Spring Data REST](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-spring-data-rest)样本分别包含一个`build.gradle`和`pom.xml`,你可能希望将其用作参考。配置的关键部分在以下清单中进行了描述: + +Maven + +``` + (1) + org.springframework.restdocs + spring-restdocs-mockmvc + {project-version} + test + + + + + (2) + org.asciidoctor + asciidoctor-maven-plugin + 1.5.8 + + + generate-docs + prepare-package (3) + + process-asciidoc + + + html + book + + + + + (4) + org.springframework.restdocs + spring-restdocs-asciidoctor + {project-version} + + + + + +``` + +|**1**|在`test`范围中添加对`spring-restdocs-mockmvc`的依赖关系。
如果你想使用`WebTestClient`或 Rest Assured 而不是 MockMVC,请分别在`spring-restdocs-webtestclient`或`spring-restdocs-restassured`上添加依赖关系。| +|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|**2**|添加 ASCIIDoctor 插件。| +|**3**|使用`prepare-package`允许文档[包含在包中](#getting-started-build-configuration-packaging-the-documentation)。| +|**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-webtestclient`或`spring-restdocs-restassured`上添加依赖关系。| +|**5** |配置一个属性来定义生成的片段的输出位置。| +|**6** |配置`test`任务,将 snippets 目录添加为输出。| +|**7** |配置`asciidoctor`任务。| +|**8** |将 snippets 目录配置为输入。| +|**9** |为扩展配置`asciidoctorExt`配置的使用。| +|**10**|使任务依赖于测试任务,以便在创建文档之前运行测试。| + +#### [打包文档](#getting-started-build-configuration-packaging-the-documentation) + +你可能希望将生成的文档打包到项目的 JAR 文件中——例如,在 Spring 启动时将其[用作静态内容](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-static-content)。要做到这一点,请配置项目的构建,以便: + +1. 文档是在构建 JAR 之前生成的。 + +2. 生成的文档包含在 JAR 中 + +下面的列表显示了如何在 Maven 和 Gradle 中实现这一点: + +Maven + +``` + (1) + org.asciidoctor + asciidoctor-maven-plugin + + + (2) + maven-resources-plugin + 2.7 + + + copy-resources + prepare-package + + copy-resources + + (3) + + ${project.build.outputDirectory}/static/docs + + + + + ${project.build.directory}/generated-docs + + + + + + + +``` + +|**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`目录中。| + +### [生成文档片段](#getting-started-documentation-snippets) + +Spring REST DOCS 使用 Spring MVC 的, Spring WebFlux 的[](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.0.x/ Spring-Framework-Reference/Testing.html#WebTestClient),或向你正在记录的服务发出请求。然后,它为请求和结果响应生成文档片段。 + +#### [设置你的测试](#getting-started-documentation-snippets-setup) + +你如何设置测试取决于你所使用的测试框架。 Spring REST DOCS 为 JUnit4 和 JUnit5 提供一流的支持。也支持其他框架,例如 TestNG,尽管需要稍多的设置。 + +##### [设置你的 JUnit4 测试](#getting-started-documentation-snippets-setup-junit) + +当使用 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。有关更多信息,请参见[配置部分](#configuration)。 + +##### [设置你的 JUnit5 测试](#getting-started-documentation-snippets-setup-junit-5) + +当使用 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。有关更多信息,请参见[配置部分](#configuration)。 + +##### [在不使用 JUnit 的情况下设置测试](#getting-started-documentation-snippets-setup-manual) + +不使用 JUnit 时的配置在很大程度上与使用 JUnit 时的配置相似。这一节描述了主要的区别。[测试样本](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/testng)也说明了这种方法。 + +第一个区别是,你应该使用`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](#getting-started-documentation-snippets-invoking-the-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`的目录(位于配置的输出目录下面)。
片段由`ExchangeResult`的`Consumer`所写。
你可以从`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**|断言服务产生了预期的响应。| + +默认情况下,编写了六个片段: + +* `/index/curl-request.adoc` + +* `/index/http-request.adoc` + +* `/index/http-response.adoc` + +* `/index/httpie-request.adoc` + +* `/index/request-body.adoc` + +* `/index/response-body.adoc` + +参见[记录你的 API](#documenting-your-api),以获取有关这些片段和可由 Spring REST DOCS 产生的其他片段的更多信息。 + +### [使用片段](#getting-started-using-the-snippets) + +在使用生成的代码片段之前,你必须创建一个`.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`| + +然后,可以使用[包括宏](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files)将生成的片段包括在手动创建的 ASCIIDoc 文件中(在本节前面描述)。可以使用`snippets`属性,该属性由`spring-restdocs-asciidoctor`中配置的[构建配置](#getting-started-build-configuration)自动设置,以引用片段输出目录。下面的示例展示了如何做到这一点: + +``` +include::{snippets}/index/curl-request.adoc[] +``` + +## [记录你的 API](#documenting-your-api) + +本节提供了有关使用 Spring REST DOCS 来记录 API 的更多详细信息。 + +### [Hypermedia](#documenting-your-api-hypermedia) + +Spring REST DOCS 提供了对[基于超媒体的](https://en.wikipedia.org/wiki/HATEOAS)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**|期望一个`rel`是`alpha`的链接。
在`linkWithRel`上使用静态`linkWithRel`方法。| +|**3**|期望一个链接的`rel`是`bravo`。| + +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**|期望一个其`rel`是`alpha`的链接。
在`linkWithRel`上使用静态`linkWithRel`方法。| +|**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 DOCS 以生成描述响应链接的片段。
在`org.springframework.restdocs.hypermedia.HypermediaDocumentation`上使用静态`links`方法。| +|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|**2**|期望一个`rel`是`alpha`的链接。
在`org.springframework.restdocs.hypermedia.HypermediaDocumentation`上使用静态`linkWithRel`方法。| +|**3**|期望一个链接的`rel`是`bravo`。| + +结果是一个名为`links.adoc`的片段,其中包含一个描述资源链接的表。 + +| |如果响应中的链接具有`title`,则可以从其描述符中省略该描述,并使用`title`。
如果省略该描述且该链接没有`title`,则会发生故障。| +|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +在记录链接时,如果在响应中发现了未记录的链接,则测试将失败。类似地,如果在响应中未找到文档化的链接,并且该链接未标记为可选的,则测试也会失败。 + +如果不想记录链接,可以将其标记为“忽略”。这样做可以防止它出现在生成的代码片段中,同时避免上述的故障。 + +你还可以在放松模式中记录链接,在这种模式中,任何未记录的链接都不会导致测试失败。要做到这一点,请在`org.springframework.restdocs.hypermedia.HypermediaDocumentation`上使用`relaxedLinks`方法。当你只想关注链接的一个子集时,记录特定的场景时,这可能会很有用。 + +#### [超媒体链接格式](#documenting-your-api-hypermedia-link-formats) + +默认情况下可以理解两种链接格式: + +* 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`接口的实现,以从响应中提取链接。 + +#### [忽略公共链接](#documenting-your-api-hypermedia-ignoring-common-links) + +在使用 HAL 时,你可能希望在概述部分对它们进行一次文档记录,然后在 API 文档的其余部分中忽略它们,而不是对每个响应都通用的链接进行文档记录,例如`self`和`curies`。为此,你可以构建[支持重用代码片段](#documenting-your-api-reusing-snippets),将链接描述符添加到预先配置为忽略某些链接的片段中。下面的示例展示了如何做到这一点: + +``` +public static LinksSnippet links(LinkDescriptor... descriptors) { + return HypermediaDocumentation.links(linkWithRel("self").ignored().optional(), linkWithRel("curies").ignored()) + .and(descriptors); +} +``` + +### [请求和响应有效载荷](#documenting-your-api-request-response-payloads) + +除了特定于超媒体的支持[前面描述的](#documenting-your-api-hypermedia)外,还提供了对请求和响应有效负载的一般文档的支持。 + +默认情况下, Spring REST DOCS 自动生成用于请求的主体和响应的主体的片段。这些片段分别命名为`request-body.adoc`和`response-body.adoc`。 + +#### [请求和响应字段](#documenting-your-api-request-response-payloads-fields) + +为了提供请求或响应有效负载的更详细的文档,提供了对有效负载字段进行文档记录的支持。 + +考虑以下有效载荷: + +``` +{ + "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.email`和`contact.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.email`和`contact.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.email`和`contact.name`现在也被视为已被记录。
在`org.springframework.restdocs.payload.PayloadDocumentation`上使用静态`subsectionWithPath`方法。| +|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +`subsectionWithPath`对于提供有效载荷的特定部分的高级概述很有用。然后,你可以为一个小节生成单独的、更详细的文档。见[记录请求或响应有效负载的一个分节](#documenting-your-api-request-response-payloads-subsections)。 + +如果你根本不想记录某个字段或小节,可以将其标记为“忽略”。这可以防止它出现在生成的代码片段中,同时避免前面描述的故障。 + +你还可以在放松模式下记录字段,在这种模式下,任何未记录的字段都不会导致测试失败。为此,在`org.springframework.restdocs.payload.PayloadDocumentation`上使用`relaxedRequestFields`和`relaxedResponseFields`方法。在记录一个特定的场景时,如果你希望只关注有效负载的一个子集,这可能会很有用。 + +| |默认情况下, Spring REST DOCS 假定你正在记录的负载是 JSON。
如果你想记录 XML 负载,请求或响应的内容类型必须与`application/xml`兼容。| +|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +##### [JSON 有效载荷中的字段](#documenting-your-api-request-response-payloads-fields-json) + +本节介绍如何使用 JSON 有效负载中的字段。 + +###### [JSON 字段路径](#documenting-your-api-request-response-payloads-fields-json-field-paths) + +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` |包含字符串`one`和`two`的数组| +| `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 字段类型](#documenting-your-api-request-response-payloads-fields-json-field-types) + +Spring 在记录字段时,REST DOCS 试图通过检查有效负载来确定其类型。支持七种不同的类型: + +| Type | Description | +|---------|---------------------------------------------------------------------------------| +| `array` |字段的每一次出现的值都是一个数组。| +|`boolean`|字段的每个出现的值都是布尔(`true`或`false`)。| +|`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 有效负载](#documenting-your-api-request-response-payloads-fields-xml) + +本节介绍如何使用 XML 有效负载。 + +###### [XML 字段路径](#documenting-your-api-request-response-payloads-fields-xml-field-paths) + +使用 XPath 描述 XML 字段路径。`/`用于下降到子节点。 + +###### [XML 字段类型](#documenting-your-api-request-response-payloads-fields-xml-field-types) + +在记录 XML 有效负载时,必须使用`FieldDescriptor`上的`type(Object)`方法为字段提供类型。文档中使用了所提供类型的`toString`方法的结果。 + +##### [重用字段描述符](#documenting-your-api-request-response-payloads-fields-reusing-field-descriptors) + +除了对[重用片段](#documenting-your-api-reusing-snippets)的一般支持外,请求和响应片段还允许使用路径前缀配置其他描述符。这使得请求或响应有效负载的重复部分的描述符可以创建一次,然后重用。 + +考虑返回一本书的端点: + +``` +{ + "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") }; +``` + +然后,你可以使用它们来记录一本书,如下所示: + +MockMVC + +``` +this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andDo(document("book", responseFields(book))); (1) +``` + +|**1**|通过使用现有描述符,文档`title`和`author`| +|-----|-----------------------------------------------------------| + +WebTestClient + +``` +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`| +|-----|-----------------------------------------------------------| + +你还可以使用描述符记录一组图书,如下所示: + +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`使用带`[].`前缀的现有描述符| + +#### [记录请求或响应有效负载的一个分节](#documenting-your-api-request-response-payloads-subsections) + +如果有效载荷很大或结构复杂,那么记录有效载荷的各个部分可能会很有用。REST DOCS 允许你通过提取有效负载的一个小节,然后将其记录下来来实现这一点。 + +##### [记录请求或响应机构的一个部分](#documenting-your-api-request-response-payloads-subsections-body) + +考虑以下 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`上使用静态`responseBody`和`beneathPath`方法。
为请求体产生一个片段。
可以使用`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`上使用静态`responseBody`和`beneathPath`方法。
为请求主体生成一个片段。可以使用`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`的片段。 + +##### [记录请求或响应的一个分节的字段](#documenting-your-api-request-response-payloads-subsections-fields) + +除了记录请求或响应主体的一个小节外,你还可以记录特定小节中的字段。你可以生成一个片段,该片段记录`temperature`对象(`high`和`low`)的字段,如下所示: + +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**|记录`high`和`low`字段。| + +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**|记录`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`上使用静态`beneathPath`方法。| +|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|**2**|记录`high`和`low`字段。| + +结果是一个片段,其中包含一个表,该表描述`high`和`low`的`weather.temperature`字段。为了使代码片段的名称不同,还包含了该小节的标识符。默认情况下,这个标识符是`beneath-${path}`。例如,前面的代码会产生一个名为`response-fields-beneath-weather.temperature.adoc`的代码片段。 + +### [请求参数](#documenting-your-api-request-parameters) + +你可以使用`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**|在查询字符串中,使用两个参数`GET`和`per_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**|在查询字符串中,使用两个参数`GET`和`per_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**|在查询字符串中,使用两个参数`GET`和`per_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 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`方法。在记录一个特定的场景时,如果你只想关注请求参数的一个子集,这可能会很有用。 + +### [路径参数](#documenting-your-api-path-parameters) + +你可以使用`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**|使用两个路径参数`GET`和`longitude`执行`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`请求,`latitude`和`longitude`。| +|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|**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**|使用两个路径参数`GET`和`longitude`执行`GET`请求。| + +结果是一个名为`path-parameters.adoc`的片段,其中包含一个表,该表描述了资源所支持的路径参数。 + +| |如果使用 MockMVC,要使路径参数对文档可用,你必须使用`RestDocumentationRequestBuilders`而不是`MockMvcRequestBuilders`上的一个方法来构建请求。| +|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +在记录路径参数时,如果在请求中使用了未记录的路径参数,则测试将失败。类似地,如果在请求中找不到文档化的路径参数,并且路径参数未标记为可选的,则测试也会失败。 + +你还可以在放松模式中记录路径参数,在这种模式中,任何未记录的参数都不会导致测试失败。要做到这一点,请在`org.springframework.restdocs.request.RequestDocumentation`上使用`relaxedPathParameters`方法。在记录一个特定的场景时,如果你只想关注路径参数的一个子集,这可能会很有用。 + +如果不想记录路径参数,可以将其标记为“忽略”。这样做可以防止它出现在生成的代码片段中,同时避免前面描述的故障。 + +### [请求零件](#documenting-your-api-request-parts) + +你可以使用`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 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`方法。在记录一个特定的场景时,如果你只想关注请求部分的一个子集,这可能会很有用。 + +如果不想记录请求部分,可以将其标记为“忽略”。这可以防止它出现在生成的代码片段中,同时避免前面描述的故障。 + +### [请求部分有效载荷](#documenting-your-api-request-parts-payloads) + +你可以用与[请求的有效载荷](#documenting-your-api-request-response-payloads)几乎相同的方式记录请求部分的有效负载,并支持记录请求部分的主体及其字段。 + +#### [记录请求部分的主体](#documenting-your-api-request-parts-payloads-body) + +你可以生成一个包含请求部分主体的片段,如下所示: + +MockMvc + +``` +MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png", "<>".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 multipartData = new LinkedMultiValueMap<>(); +Resource imageResource = new ByteArrayResource("<>".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 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`的片段。 + +#### [记录请求部分的字段](#documenting-your-api-request-parts-payloads-fields) + +你可以用与请求或响应的字段相同的方式记录请求部分的字段,如下所示: + +MockMvc + +``` +MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png", "<>".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 multipartData = new LinkedMultiValueMap<>(); +Resource imageResource = new ByteArrayResource("<>".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 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 的有效负载的更多信息,请参见[关于记录请求和响应有效载荷的部分](#documenting-your-api-request-response-payloads)。 + +### [HTTP 头](#documenting-your-api-http-headers) + +你可以通过分别使用`requestHeaders`和`responseHeaders`来记录请求或响应中的头。下面的例子说明了如何做到这一点: + +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 头文件时,如果在请求或响应中找不到已记录的头文件,则测试将失败。 + +### [重用片段](#documenting-your-api-reusing-snippets) + +对于正在文档中的 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`值的`first`、`last`、`next`、`previous`、`alpha`和`bravo`的链接都是有文档记录的。 + +### [记录约束](#documenting-your-api-constraints) + +Spring REST DOCS 提供了许多类,这些类可以帮助你记录约束。你可以使用`ConstraintDescriptions`的实例来访问类的约束的描述。下面的示例展示了如何做到这一点: + +``` +public void example() { + ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class); (1) + List 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)类显示了这一功能。 + +#### [寻找约束](#documenting-your-api-constraints-finding) + +默认情况下,通过使用 Bean 验证`Validator`来找到约束。目前,只支持属性约束。你可以通过使用自定义的`ValidatorConstraintResolver`实例创建`ConstraintDescriptions`来定制`Validator`所使用的`Validator`。要完全控制约束解析,可以使用自己的`ConstraintResolver`实现。 + +#### [描述约束](#documenting-your-api-constraints-describing) + +对于 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 的样本包含[这样的资源包的一个例子](https://github.com/spring-projects/spring-restdocs/tree/v2.0.6.RELEASE/samples/rest-notes-spring-hateoas/src/test/resources/org/springframework/restdocs/constraints/ConstraintDescriptions.properties)。 + +资源包中的每个键都是约束的完全限定名加上`.description`。例如,标准`@NotNull`约束的键是`javax.validation.constraints.NotNull.description`。 + +你可以在约束的描述中使用引用约束属性的属性占位符。例如,`@Min`约束的默认描述`Must be at least ${value}`是指约束的`value`属性。 + +要获得约束描述解析的更多控制,可以使用自定义`ConstraintDescriptions`创建`ResourceBundleConstraintDescriptionResolver`。要获得完全的控制,你可以使用自定义的`ConstraintDescriptions`实现来创建`ConstraintDescriptionResolver`。 + +#### [在生成的代码片段中使用约束描述](#_using_constraint_descriptions_in_generated_snippets) + +一旦有了约束的描述,你就可以在生成的代码片段中随意使用它们。例如,你可能希望将约束描述作为字段描述的一部分。或者,你可以在请求字段片段中以[额外信息](#documenting-your-api-customizing-including-extra-information)的形式包含约束。 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)类说明了后一种方法。 + +### [默认片段](#documenting-your-api-default-snippets) + +当你记录请求和响应时,会自动生成许多代码片段。 + +| 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` |包含返回的响应的主体。| + +你可以配置默认情况下产生的代码段。有关更多信息,请参见[配置部分](#configuration)。 + +### [使用参数化输出目录](#documentating-your-api-parameterized-output-directories) + +当使用 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(); +} +``` + +有了这种配置,对你正在测试的服务的每次调用都会产生[默认片段](#documenting-your-api-default-snippets),而不需要进行任何进一步的配置。看看每个示例应用程序中的`GettingStartedDocumentation`类,就可以看到该功能的实际应用。 + +### [自定义输出](#documenting-your-api-customizing) + +本节描述如何自定义 Spring REST DOCS 的输出。 + +#### [自定义生成的代码片段](#documenting-your-api-customizing-snippets) + +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`的模板。 + +#### [包括额外的信息](#documenting-your-api-customizing-including-extra-information) + +有两种方法可以提供额外的信息以包含在生成的代码片段中: + +* 使用描述符上的`attributes`方法向其添加一个或多个属性。 + +* 在调用`curlRequest`、`httpRequest`、`httpResponse`时传入一些属性,以此类推。这样的属性与代码片段作为一个整体相关联。 + +在模板呈现过程中,任何附加属性都是可用的。再加上自定义的代码片段模板,这使得在生成的代码片段中包含额外的信息成为可能。 + +一个具体的例子是,在记录请求字段时,添加了一个约束列和一个标题。第一步是为你记录的每个字段提供`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`属性。| + +## [定制请求和响应](#customizing-requests-and-responses) + +在某些情况下,你可能不希望将请求记录为与发送的请求完全一致,或者不希望将响应记录为与接收的请求完全一致。 Spring REST DOCS 提供了许多可用于在请求或响应被文档化之前对其进行修改的预处理器。 + +通过调用`document`和`OperationRequestPreprocessor`或`OperationResponsePreprocessor`来配置预处理。可以通过在`Preprocessors`上使用静态`preprocessRequest`和`preprocessResponse`方法获得实例。下面的例子说明了如何做到这一点: + +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`方法中使用`RestDocumentationConfigurer`API 来配置预处理器。例如,要从所有请求中删除`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](#customizing-requests-and-responses-preprocessors)。 + +### [预处理器](#customizing-requests-and-responses-preprocessors) + +#### [漂亮的印刷](#customizing-requests-and-responses-preprocessors-pretty-print) + +`prettyPrint`on`Preprocessors`格式化请求或响应的内容,以使其更易于阅读。 + +#### [掩蔽链接](#customizing-requests-and-responses-preprocessors-mask-links) + +如果你正在记录一个基于超媒体的 API,你可能希望鼓励客户机通过使用链接而不是通过使用硬编码的 URI 来导航该 API。这样做的一种方法是限制在文档中使用 URI。`maskLinks`on`Preprocessors`将响应中任何链接的`href`替换为`…​`。如果你愿意,你还可以指定一个不同的替代品。 + +#### [删除页眉](#customizing-requests-and-responses-preprocessors-remove-headers) + +`removeHeaders`on`Preprocessors`从请求或响应中删除任何标题,其中名称等于任何给定的标题名称。 + +`removeMatchingHeaders`on`Preprocessors`删除请求或响应中名称与给定正则表达式模式匹配的任何标题。 + +#### [替换模式](#customizing-requests-and-responses-preprocessors-replace-patterns) + +`replacePattern`on`Preprocessors`提供了用于替换请求或响应中的内容的通用机制。任何与正则表达式匹配的事件都将被替换。 + +#### [修改请求参数](#customizing-requests-and-responses-preprocessors-modify-request-parameters) + +你可以在`Preprocessors`上使用`modifyParameters`来添加、设置和删除请求参数。 + +#### [修改 URI](#customizing-requests-and-responses-preprocessors-modify-uris) + +| |如果使用 MockMVC 或未绑定到服务器的 WebTestClient,则应通过[更改配置](#configuration-uris)自定义 URI。| +|---|----------------------------------------------------------------------------------------------------------------------------------------------------| + +你可以在`Preprocessors`上使用`modifyUris`来修改请求或响应中的任何 URI。当使用绑定到服务器的 Rest Assured 或 WebTestClient 时,这允许你在测试服务的本地实例时自定义文档中出现的 URI。 + +#### [编写自己的预处理器](#customizing-requests-and-responses-preprocessors-writing-your-own) + +如果一个内置的预处理器不能满足你的需求,你可以通过实现`OperationPreprocessor`接口来编写自己的预处理器。然后,你可以以与任何内置预处理器完全相同的方式使用你的定制预处理器。 + +如果只想修改请求或响应的内容(主体),可以考虑实现`ContentModifier`接口,并将其与内置的`ContentModifyingOperationPreprocessor`一起使用。 + +## [配置](#configuration) + +本节介绍如何配置 Spring REST DOCS。 + +### [记录的 URI](#configuration-uris) + +本节介绍如何配置有文档的 URI。 + +#### [MockMVC URI 定制](#configuration-uris-mockmvc) + +在使用 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 定制](#configuration-uris-rest-assured) + +Rest Assured 通过发出实际的 HTTP 请求来测试服务。因此,在对服务执行操作之后,但在对其进行文档记录之前,必须对 URI 进行自定义。a[Rest Assured-Specific 预处理器](#customizing-requests-and-responses-preprocessors-modify-uris)用于此目的。 + +#### [WebTestClient URI 自定义](#configuration-uris-webtestclient) + +当使用 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)`。| +|-----|-------------------------------------------------------------------------------------------------| + +### [片段编码](#configuration-snippet-encoding) + +默认的代码片段编码是`UTF-8`。你可以使用`RestDocumentationConfigurer`API 更改默认的代码片段编码。例如,以下示例使用`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`。| +|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +### [片断模板格式](#configuration-snippet-template-format) + +默认的代码片段模板格式是 ASCIIDoctor。Markdown 还支持开箱即用。你可以使用`RestDocumentationConfigurer`API 更改默认格式。下面的例子说明了如何做到这一点: + +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(); +``` + +### [默认片段](#configuration-default-snippets) + +默认情况下会产生六个片段: + +* `curl-request` + +* `http-request` + +* `http-response` + +* `httpie-request` + +* `request-body` + +* `response-body` + +在安装过程中,可以使用`RestDocumentationConfigurer`API 更改默认的代码段配置。以下示例默认情况下仅生成`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(); +``` + +### [默认操作预处理器](#configuration-default-preprocessors) + +在安装过程中,可以使用`RestDocumentationConfigurer`API 配置默认的请求和响应预处理器。以下示例从所有请求中删除`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 合作](#working-with-asciidoctor) + +本节描述了与 Spring REST DOCS 特别相关的与 ASCIIDoctor 合作的方面。 + +| |ASCIIDoc 是文档格式。
ASCIIDoctor 是从 ASCIIDoc 文件(以`.adoc`结尾)生成内容(通常是 HTML)的工具。| +|---|--------------------------------------------------------------------------------------------------------------------------------------------------| + +### [Resources](#working-with-asciidoctor-resources) + +* [语法快速引用](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference) + +* [用户手册](https://asciidoctor.org/docs/user-manual) + +### [包括片段](#working-with-asciidoctor-including-snippets) + +本节介绍如何包含 ASCIIDoc 片段。 + +#### [包括一个操作的多个片段](#working-with-asciidoctor-including-snippets-operation) + +你可以使用名为`operation`的宏导入为特定操作生成的全部或部分片段。它可以通过在项目的[构建配置](#getting-started-build-configuration)中包含`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[] +``` + +##### [章节标题](#working-with-asciidoctor-including-snippets-operation-titles) + +对于使用`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 +``` + +#### [包括单个片段](#working-with-asciidoctor-including-snippets-individual) + +[包括宏](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files)用于在文档中包含单个片段。可以使用`snippets`属性(由[构建配置](#getting-started-build-configuration)中配置的`spring-restdocs-asciidoctor`自动设置)来引用 snippets 输出目录。下面的示例展示了如何做到这一点: + +``` +include::{snippets}/index/curl-request.adoc[] +``` + +### [自定义表格](#working-with-asciidoctor-customizing-tables) + +许多片段在其默认配置中包含一个表。表的外观可以自定义,可以在包含代码片段时提供一些附加配置,也可以使用自定义代码片段模板。 + +#### [格式化列](#working-with-asciidoctor-customizing-tables-formatting-columns) + +ASCIIDoctor 对[格式化表格的列](https://asciidoctor.org/docs/user-manual/#cols-format)提供了丰富的支持。如下例所示,你可以使用`cols`属性指定表中列的宽度: + +``` +[cols="1,3"] (1) +include::{snippets}/index/links.adoc[] +``` + +|**1**|表格的宽度分为两列,第二列的宽度是第一列的三倍。| +|-----|-----------------------------------------------------------------------------------------------------------------| + +#### [配置标题](#working-with-asciidoctor-customizing-tables-title) + +你可以使用以`.`为前缀的行来指定表格的标题。下面的示例展示了如何做到这一点: + +``` +.Links (1) +include::{snippets}/index/links.adoc[] +``` + +|**1**|表格的标题将是`Links`。| +|-----|----------------------------------| + +#### [避免表格式问题](#working-with-asciidoctor-customizing-tables-formatting-problems) + +ASCIIDoctor 使用`|`字符对表格中的单元格进行划界。如果你希望`|`出现在单元格的内容中,这可能会导致问题。你可以通过使用反斜杠转义`|`来避免这个问题——换句话说,使用`\|`而不是`|`。 + +所有默认的 ASCIIDoctor 片段模板都使用名为`tableCellContent`的小胡子 lamba 自动执行这个转义。如果你编写自己的自定义模板,你可能想要使用这个 lamba。下面的示例展示了如何在包含`description`属性的值的单元格中转义`|`字符: + +``` +| {{#tableCellContent}}{{description}}{{/tableCellContent}} +``` + +### [进一步阅读](#working-with-asciidoctor-further-reading) + +有关自定义表的更多信息,请参见[ASCIIDoctor 用户手册中的表格部分](https://asciidoctor.org/docs/user-manual/#tables)。 + +## [与 Markdown 合作](#working-with-markdown) + +本节描述了与 Spring REST DOCS 特别相关的使用 Markdown 的方面。 + +### [局限性](#working-with-markdown-limitations) + +Markdown 最初是为人们为网络写作而设计的,因此,它并不像 ASCIIDoctor 那样适合编写文档。通常,这些限制可以通过使用另一个构建在 Markdown 之上的工具来克服。 + +Markdown 没有对表格的官方支持。 Spring REST DOCS 的默认 Markdown 片段模板使用[Markdown Extra 的表格格式](https://michelf.ca/projects/php-markdown/extra/#table)。 + +### [包括片段](#working-with-markdown-including-snippets) + +Markdown 没有内置支持将一个 Markdown 文件包含在另一个文件中。要将生成的 Markdown 片段包含在文档中,你应该使用支持此功能的附加工具。一个特别适合于记录 API 的示例是[Slate](https://github.com/tripit/slate)。 + +## [贡献](#contributing) + +Spring REST DOCS 旨在使你能够轻松地为你的 RESTful 服务生成高质量的文档。然而,没有你们的贡献,我们就无法实现这一目标。 + +### [Questions](#contributing-questions) + +通过使用`spring-restdocs`标记,可以在[堆栈溢出](https://stackoverflow.com)上询问有关 Spring REST DOCS 的问题。同样,我们鼓励你通过回答问题来帮助你的同伴 Spring REST DOCS 用户。 + +### [Bugs](#contributing-bugs) + +如果你认为你发现了一个 bug,请花点时间搜索。如果没有其他人报告了该问题,请[打开新的一期](https://github.com/spring-projects/spring-restdocs/issues/new)详细描述该问题,并在理想情况下包含一个复制该问题的测试。 + +### [增强功能](#contributing-enhancements) + +如果你希望对 Spring REST DOCS 进行增强,那么最受欢迎的是拉请求。源代码位于[GitHub](https://github.com/spring-projects/spring-restdocs)上。你可能希望搜索[现有问题](https://github.com/spring-projects/spring-restdocs/issues?q=is%3Aissue)和[拉请求](https://github.com/spring-projects/spring-restdocs/pulls?q=is%3Apr),以查看是否已经提出了增强。你可能还希望[打开新的一期](https://github.com/spring-projects/spring-restdocs/issues/new)在开始工作之前讨论可能的增强。 \ No newline at end of file -- GitLab