使用 Restdocs 创建 API 文档

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

您将构建什么

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

您需要什么

如何完成本指南

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

从头开始,请继续执行 使用 Spring Initializr 开始

跳过基础知识,请执行以下操作

完成时,您可以将您的结果与 gs-testing-restdocs/complete 中的代码进行核对。

使用 Spring Initializr 开始

您可以使用此 预初始化项目 并点击生成以下载 ZIP 文件。此项目配置为适合本教程中的示例。

要手动初始化项目

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

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

  3. 点击依赖项并选择Spring Web

  4. 点击生成

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

如果您的 IDE 集成了 Spring Initializr,则可以从您的 IDE 中完成此过程。
您也可以从 Github 分叉项目并在您的 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 为您处理所有这些。

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

测试应用程序

现在应用程序已启动并运行,您可以对其进行测试。您可以在 https://127.0.0.1:8080 加载主页。但是,为了让您在进行更改时更有信心应用程序可以正常工作,您需要自动化测试。您还需要发布 HTTP 端点的文档。您可以通过使用 Spring REST Docs 将动态测试部分作为测试的一部分生成。

首先,您可以编写一个简单的健全性检查测试,如果应用程序上下文无法启动,则该测试将失败。为此,请将 Spring Test 和 Spring REST Docs 作为依赖项添加到您的项目中,在测试范围内。以下清单显示了如果您使用 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 https://127.0.0.1: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}。在这种简单情况下,唯一的其他标记是顶部的 =(它是 1 级标题)以及代码片段上标题(“请求”和“响应”)之前的 .. 将该行上的文本转换为标题。

然后,在构建配置中,您需要将此源文件处理成您选择的文档格式。例如,您可以使用 Maven 生成 HTML(target/generated-docs 在您执行 ./mvnw package 时生成)。以下清单显示了 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,build/asciidoc 在您运行 ./gradlew asciidoctor 时生成。以下清单显示了 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 许可证发布代码,并以 署名-非衍生作品创作共用许可证 发布文字。

获取代码