使用 Restdocs 创建 API 文档

本指南将引导您完成为 Spring 应用程序中的 HTTP 端点生成文档的过程。

您将构建什么

您将构建一个简单的 Spring 应用程序,其中包含一些公开 API 的 HTTP 端点。您将仅使用 JUnit 和 Spring 的 MockMvc 测试 Web 层。然后,您将使用相同的测试通过 Spring REST Docs 为 API 生成文档。

您需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始完成每个步骤,也可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可以工作的代码。

从头开始,请继续阅读 从 Spring Initializr 开始

跳过基础部分,请执行以下操作

完成后,您可以对照 gs-testing-restdocs/complete 中的代码检查您的结果。

从 Spring Initializr 开始

您可以使用这个预初始化的项目,然后点击 Generate 下载 ZIP 文件。这个项目已经配置好,适合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。这个服务会引入应用程序所需的所有依赖,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您想使用的语言。本指南假定您选择了 Java。

  3. 点击 Dependencies 并选择 Spring Web

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,它是一个根据您的选择配置好的 Web 应用程序的压缩包。

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。
您也可以从 Github Fork 项目,然后在您的 IDE 或其他编辑器中打开它。

创建一个简单的应用程序

为您的 Spring 应用程序创建一个新的控制器。以下列表(来自 src/main/java/com/example/testingrestdocs/HomeController.java)展示了如何实现:

package com.example.testingrestdocs;

import java.util.Collections;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

	@GetMapping("/")
	public Map<String, Object> greeting() {
		return Collections.singletonMap("message", "Hello, World");
	}

}

运行应用程序

Spring Initializr 会创建一个 main 类,您可以使用它来启动应用程序。以下列表(来自 src/main/java/com/example/testingrestdocs/TestingRestdocsApplication.java)展示了 Spring Initializr 创建的应用程序类:

package com.example.testingrestdocs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestingRestdocsApplication {

	public static void main(String[] args) {
		SpringApplication.run(TestingRestdocsApplication.class, args);
	}
}

@SpringBootApplication 是一个便捷注解,它包含了以下所有功能:

  • @Configuration: 将类标记为应用程序上下文的 Bean 定义来源。

  • @EnableAutoConfiguration: 告诉 Spring Boot 根据类路径设置、其他 Bean 和各种属性设置开始添加 Bean。

  • @EnableWebMvc: 将应用程序标记为 Web 应用程序并激活关键行为,例如设置 DispatcherServlet。Spring Boot 在类路径中检测到 spring-webmvc 时会自动添加它。

  • @ComponentScan: 告诉 Spring 在 com.example.testingrestdocs 包中查找其他组件、配置和服务,从而找到 HelloController 类。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。您注意到一行 XML 代码都没有吗?也没有 web.xml 文件。这个 Web 应用程序是 100% 纯 Java 的,您无需处理任何管道或基础设施的配置。Spring Boot 会为您处理所有这些。

日志输出将会显示。服务应该在几秒钟内启动并运行。

测试应用程序

应用程序现在正在运行,您可以测试它。您可以在 http://localhost:8080 加载主页。然而,为了让您在进行更改时对应用程序的工作更有信心,您希望自动化测试过程。您还希望发布 HTTP 端点的文档。您可以使用 Spring REST Docs 作为测试的一部分生成这些测试的动态部分。

您首先可以做的是编写一个简单的健康检查测试,如果应用程序上下文无法启动,它就会失败。为此,请将 Spring Test 和 Spring REST Docs 添加为项目的依赖项,作用域为 test。以下列表显示了如果您使用 Maven 需要添加的内容:

<dependency>
  <groupId>org.springframework.restdocs</groupId>
  <artifactId>spring-restdocs-mockmvc</artifactId>
  <scope>test</scope>
</dependency>

以下列表显示了完整的 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>gs-testing-restdocs</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- tag::test[] -->
		<dependency>
		  <groupId>org.springframework.restdocs</groupId>
		  <artifactId>spring-restdocs-mockmvc</artifactId>
		  <scope>test</scope>
		</dependency>
		<!-- end::test[] -->
	</dependencies>

	<build>
		<plugins>
			<!-- tag::asciidoc[] -->
			<plugin>
				<groupId>org.asciidoctor</groupId>
				<artifactId>asciidoctor-maven-plugin</artifactId>
				<version>1.5.8</version>
				<executions>
					<execution>
						<id>generate-docs</id>
						<phase>prepare-package</phase>
						<goals>
							<goal>process-asciidoc</goal>
						</goals>
						<configuration>
							<backend>html</backend>
							<doctype>book</doctype>
						</configuration>
					</execution>
				</executions>
				<dependencies>
					<dependency>
						<groupId>org.springframework.restdocs</groupId>
						<artifactId>spring-restdocs-asciidoctor</artifactId>
						<version>${spring-restdocs.version}</version>
					</dependency>
				</dependencies>
			</plugin>
			<!-- end::asciidoc[] -->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

以下示例展示了如果您使用 Gradle 需要添加的内容:

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

以下列表显示了完整的 build.gradle 文件:

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.0'
	id 'io.spring.dependency-management' version '1.1.5'
	id 'org.asciidoctor.jvm.convert' version '2.4.0'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

ext {
	set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// tag::test[]
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
	// end::test[]
}

tasks.named('test') {
	outputs.dir snippetsDir
	useJUnitPlatform()
}

tasks.named('asciidoctor') {
	inputs.dir snippetsDir
	dependsOn test
}
您可以忽略构建文件中的注释。它们是为了方便我们选取文件中的部分内容包含到本指南中。
您已经引入了 REST Docs 的 mockmvc 版本,它使用 Spring MockMvc 来捕获 HTTP 内容。如果您自己的应用程序不使用 Spring MVC,您也可以使用 restassured 版本,它适用于全栈集成测试。

现在创建一个带有 @RunWith@SpringBootTest 注解以及一个空的测试方法的测试用例,如下面的示例所示(来自 src/test/java/com/example/testingrestdocs/TestingRestdocsApplicationTests.java):

package com.example.testingrestdocs;

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestingRestdocsApplicationTests {

	@Test
	public void contextLoads() throws Exception {
	}
}

您可以在 IDE 中或命令行(通过运行 ./mvnw test./gradlew test)运行此测试。

进行一次健康检查是件好事,但您还应该编写一些断言应用程序行为的测试。一个有用的方法是只测试 MVC 层,即 Spring 处理传入的 HTTP 请求并将其交给您的控制器的地方。为此,您可以使用 Spring 的 MockMvc,并通过在测试用例上使用 @WebMvcTest 注解来请求将其注入。以下示例(来自 src/test/java/com/example/testingrestdocs/WebLayerTest.java)展示了如何实现:

package com.example.testingrestdocs;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(HomeController.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class WebLayerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")))
				.andDo(document("home"));
	}
}

为文档生成代码片段

前面章节中的测试会发出(模拟)HTTP 请求并断言响应。您创建的 HTTP API 具有动态内容(至少原则上是这样),因此能够监视测试并提取 HTTP 请求用于文档将是非常不错的。Spring REST Docs 通过生成“代码片段”来实现这一点。您可以通过向测试添加一个注解和一个额外的“断言”来实现此功能。以下示例(来自 src/test/java/com/example/testingrestdocs/WebLayerTest.java)显示了完整的测试:

package com.example.testingrestdocs;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(HomeController.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
public class WebLayerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")))
				.andDo(document("home"));
	}
}

新的注解是 @AutoConfigureRestDocs(来自 Spring Boot),它接受一个参数,用于指定生成的代码片段的目录位置。新的断言是 MockMvcRestDocumentation.document,它接受一个参数,用于指定代码片段的字符串标识符。

Gradle 用户可能更喜欢使用 build 而不是 target 作为输出目录。不过,这并不重要。使用您喜欢的即可。

运行测试,然后查看 target/snippets 目录。您应该会找到一个名为 home(标识符)的目录,其中包含 Asciidoctor 代码片段,如下所示:

└── target
    └── snippets
        └── home
            └── curl-request.adoc
            └── http-request.adoc
            └── http-response.adoc
            └── httpie-request.adoc
            └── request-body.adoc
            └── response-body.adoc

默认的代码片段是 Asciidoctor 格式的 HTTP 请求和响应。还有 curlhttpie(两个常见且流行的命令行 HTTP 客户端)的命令行示例。

您可以通过向测试中的 document() 断言添加参数来创建额外的代码片段。例如,您可以使用 PayloadDocumentation.responseFields() 代码片段来记录 JSON 响应中的每个字段,如下面的示例所示(来自 src/test/java/com/example/testingrestdocs/WebLayerTest.java):

this.mockMvc.perform(get("/"))
    ...
    .andDo(document("home", responseFields(
        fieldWithPath("message").description("The welcome message for the user.")
    ));

如果您运行测试,您应该会找到一个额外的代码片段文件,名为 response-fields.adoc。它包含一个字段名称和描述的表格。如果您遗漏了某个字段或名称错误,测试就会失败。这就是 REST Docs 的强大之处。

您可以创建自定义代码片段、更改代码片段的格式以及自定义值,例如主机名。有关更多详细信息,请参阅 Spring REST Docs 的文档。

使用代码片段

要使用生成的代码片段,您需要在项目中包含一些 Asciidoctor 内容,然后在构建时包含这些代码片段。要看到其效果,请创建一个名为 src/main/asciidoc/index.adoc 的新文件,并按需要包含代码片段。以下示例(来自 src/main/asciidoc/index.adoc)展示了如何实现:

= Getting Started With Spring REST Docs

This is an example output for a service running at http://localhost:8080:

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

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

As you can see the format is very simple, and in fact you always get the same message.

这个 Asciidoc 文件的主要特点是它使用 Asciidoctor 的 include 指令(冒号和尾随的括号告诉解析器对这些行进行特殊处理)包含了两个代码片段。请注意,包含的代码片段的路径被表示为一个占位符(在 Asciidoctor 中称为 attribute),名为 {snippets}。在这种简单情况下,唯一的其他标记是顶部的 =(它是一个一级章节标题)以及代码片段标题(“request”和“response”)前面的 .. 会将该行上的文本转换为标题。

然后,在构建配置中,您需要将此源文件处理成您选择的文档格式。例如,您可以使用 Maven 生成 HTML(当您运行 ./mvnw package 时会生成 target/generated-docs)。以下列表显示了 pom.xml 文件中与 Asciidoc 相关的内容:

<plugin>
	<groupId>org.asciidoctor</groupId>
	<artifactId>asciidoctor-maven-plugin</artifactId>
	<version>1.5.8</version>
	<executions>
		<execution>
			<id>generate-docs</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>process-asciidoc</goal>
			</goals>
			<configuration>
				<backend>html</backend>
				<doctype>book</doctype>
			</configuration>
		</execution>
	</executions>
	<dependencies>
		<dependency>
			<groupId>org.springframework.restdocs</groupId>
			<artifactId>spring-restdocs-asciidoctor</artifactId>
			<version>${spring-restdocs.version}</version>
		</dependency>
	</dependencies>
</plugin>

如果您使用 Gradle,当您运行 ./gradlew asciidoctor 时会生成 build/asciidoc。以下列表显示了 build.gradle 文件中与 Asciidoctor 相关的内容:

plugins {
	...
	id 'org.asciidoctor.convert' version '1.5.6'
}

...

asciidoctor {
    sourceDir 'src/main/asciidoc'
    attributes \
      'snippets': file('target/snippets')
}
Gradle 中 Asciidoctor 源文件的默认位置是 src/docs/asciidoc。我们将 sourceDir 设置为与 Maven 的默认位置一致。

总结

恭喜!您刚刚开发了一个 Spring 应用程序,并使用 Spring Restdocs 为其生成了文档。您可以将创建的 HTML 文档发布到静态网站,或者将其打包并从应用程序本身提供服务。您的文档将始终保持最新,如果文档没有更新,测试将导致您的构建失败。

另请参阅

以下指南可能也有帮助:

想编写新指南或为现有指南做贡献?请查看我们的 贡献指南

所有指南的代码均采用 ASLv2 许可证发布,文本内容则采用署名-禁止演绎(Attribution, NoDerivatives)知识共享许可协议发布。

获取代码