使用 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 會為您處理所有這些。

日誌輸出將會顯示。服務應該在幾秒鐘內啟動並執行。

測試應用程式

應用程式現在正在執行,您可以測試它。您可以在 https://: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 https://: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)知識共享許可協議釋出。

獲取程式碼