使用 Restdocs 建立 API 文件

本指南將引導您完成為 Spring 應用程式中的 HTTP 端點生成文件的過程。

您將構建什麼

您將構建一個帶有 HTTP 端點的簡單 Spring 應用程式,這些端點暴露一個 API。您將僅使用 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. 點選 Dependencies 並選擇 Spring Web

  4. 單擊生成

  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 作為依賴項新增到您的專案中,在測試範圍內。以下清單顯示瞭如果您使用 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 許可,文字內容採用署名-禁止演繹知識共享許可

獲取程式碼