当前位置: 首页 > 工具软件 > restdoc > 使用案例 >

SpringBoot 2 使用 Restdocs 创建 API 文档

邢运良
2023-12-01

开篇词

该指南将引导你在 Spring 应用中为 HTTP 端点生成文档。
 

你将创建的应用

我们将使用一些暴露 API 的 HTTP 端点构建一个简单的 Spring 应用。我们将使用 JUnit 和 Spring 的 MockMvc 仅测试网络层。然后,我们将使用相同的测试通过 Spring REST Docs 生成 API 的文档。
 

你将需要的工具

如何完成这个指南

像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。

  • 要从头开始,移步至从 Spring Initializr 开始
  • 要跳过基础,执行以下操作:
    • 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:git clone https://github.com/spring-guides/gs-testing-restdocs.git
    • 切换至 gs-testing-restdocs/initial 目录;
    • 跳转至该指南的创建简单应用

待一切就绪后,可以检查一下 gs-testing-restdocs/complete 目录中的代码。
 

从 Spring Initializr 开始

对于所有的 Spring 应用来说,你应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来提取应用程序所需的依赖,并为你完成许多设置。该示例仅需要 Spring Web 依赖。下图显示了此示例项目的 Initializr 设置:

上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将 com.exampletesting-restdocs 的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。

以下清单显示了选择 Maven 时创建的 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>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>testing-restdocs</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>testing-restdocs</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</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>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

以下清单显示了在选择 Gradle 时创建的 build.gradle 文件:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

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

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

 

创建简单应用

为我们的 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 创建一个可用于启动应用的主类。以下清单(来自 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;
  • @ComponentScan:告知 Spring 在 com/example.testingrestdocs 包中寻找他组件、配置以及服务。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动应用。

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

测试应用

现在该应用正在运行,我们可以对其进行测试。我们可以在 http://localhost: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>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>testing-restdocs</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>testing-restdocs</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.restdocs</groupId>
			<artifactId>spring-restdocs-mockmvc</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

以下示例显示了使用 Gradle 时要添加的内容:

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

asciidoctor {
	sourceDir 'src/main/asciidoc'
	attributes \
		'snippets': file('target/snippets')
}

dependencies {
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

以下清单显示了完整的 build.gradle 文件:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
	id 'org.asciidoctor.convert' version '1.5.6'
}

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

asciidoctor {
	sourceDir 'src/main/asciidoc'
	attributes \
		'snippets': file('target/snippets')
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

我们可以忽略构建文件中的注释。它们在那里是让我们挑选文件的一部分以包含在该指南中。

我们包含了 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)显示了如何执行该操作:

@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class WebLayerTest {

    @Autowired
    private MockMvc mockMvc;

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

 

生成文档的片段

上一部分的测试发出(模拟)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(标识符)的目录,其中包含 Asciidocter 片段,如下所示:

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

HTTP 请求和响应的默认代码段为 Asciidoctor 格式。还有 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 中的一个属性),称为 {snippets}。在这种简单情况下,唯一的其他标记是顶部的 = (属于第一级部分标头)以及代码片段上的摘录(“请求”和“响应”)之后的点(.)。点(.)将该行上的文本变成标题。

然后,在构建配置中,我们需要将该源文件处理为所选的文档格式。例如,我们可以使用 Maven 生成 HTML(当我们执行 ./mvnw 包时,将生成 target/generated-docs)。以下清单显示 pom.xml 文件的 Asciidoc 部分:


如果使用 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/doc/asciidoc。我们将 sourceDir 设置为与 Maven 的默认值匹配。

 

概述

恭喜你!我们刚刚开发了一个 Spring 应用,并使用 Spring Restdocs 对其进行了文档记录。我们可以将创建的 HTML 文档发布到静态网站,也可以打包并让其对应用本身提供服务。我们的文档将始终是最新的,否则,测试将使我们的构建失败。
 

参见

以下指南也可能会有所帮助:

想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南

 类似资料: