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");
}
}
使用 Restdocs 建立 API 文件
本指南將引導您完成為 Spring 應用程式中的 HTTP 端點生成文件的過程。
您將構建什麼
您將構建一個帶有 HTTP 端點的簡單 Spring 應用程式,這些端點暴露一個 API。您將僅使用 JUnit 和 Spring 的 MockMvc 測試 Web 層。然後,您將使用相同的測試透過 Spring REST Docs 為 API 生成文件。
你需要什麼
-
大約 15 分鐘
-
一個喜歡的文字編輯器或 IDE
-
Java 17 或更高版本
-
您還可以將程式碼直接匯入到您的 IDE 中
如何完成本指南
與大多數 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 開始
您可以使用這個預初始化專案,然後單擊“生成”下載 ZIP 檔案。此專案已配置為適合本教程中的示例。
手動初始化專案
-
導航到 https://start.spring.io。此服務會為您拉取應用程式所需的所有依賴項,併為您完成大部分設定。
-
選擇 Gradle 或 Maven 以及您想要使用的語言。本指南假設您選擇了 Java。
-
點選 Dependencies 並選擇 Spring Web。
-
單擊生成。
-
下載生成的 ZIP 檔案,這是一個已根據您的選擇配置好的 Web 應用程式存檔。
| 如果您的 IDE 集成了 Spring Initializr,您可以從 IDE 中完成此過程。 |
| 您還可以從 Github fork 該專案並在您的 IDE 或其他編輯器中開啟它。 |
建立簡單應用程式
為您的 Spring 應用程式建立一個新的控制器。以下清單(來自 src/main/java/com/example/testingrestdocs/HomeController.java)展示瞭如何操作
執行應用程式
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 請求和響應。還有 curl 和 httpie(兩個常見且流行的命令列 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 文件釋出到靜態網站,或將其打包並從應用程式本身提供。您的文件將始終保持最新,如果不是,測試將使您的構建失敗。