使用 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

預設程式碼片段是 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 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 級的章節標題)和程式碼片段上標題(「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 授權發佈程式碼,並以 姓名標示-禁止改作創用 CC 授權 發佈寫作內容。

取得程式碼