package com.example.testingweb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@RequestMapping("/")
public @ResponseBody String greeting() {
return "Hello, World";
}
}
測試 Web 層
本指南將引導您完成建立 Spring 應用程式並使用 JUnit 進行測試的過程。
您將構建什麼
您將構建一個簡單的 Spring 應用程式並使用 JUnit 進行測試。您可能已經知道如何在應用程式中編寫和執行單個類的單元測試,因此,對於本指南,我們將重點介紹如何使用 Spring Test 和 Spring Boot 的特性來測試 Spring 與您的程式碼之間的互動。您將從一個簡單的測試開始,該測試用於驗證應用程式上下文是否成功載入,然後繼續使用 Spring 的 MockMvc
只測試 Web 層。
您需要什麼
-
大約 15 分鐘
-
一個喜歡的文字編輯器或 IDE
-
Java 1.8 或更高版本
-
您也可以將程式碼直接匯入到您的 IDE 中
如何完成本指南
與大多數 Spring 入門指南一樣,您可以從頭開始完成每個步驟,也可以跳過您已經熟悉的基本設定步驟。無論哪種方式,您最終都會得到可工作的程式碼。
要從頭開始,請轉到從 Spring Initializr 開始。
要跳過基礎部分,請執行以下操作
-
下載並解壓本指南的原始碼倉庫,或使用 Git 克隆:
git clone https://github.com/spring-guides/gs-testing-web.git
-
進入目錄
gs-testing-web/initial
-
跳到建立簡單的應用程式。
完成時,您可以對照 gs-testing-web/complete
中的程式碼檢查您的結果。
從 Spring Initializr 開始
您可以使用這個預初始化的專案,然後點選 Generate 下載一個 ZIP 檔案。該專案已配置為與本教程中的示例相符。
手動初始化專案
-
導航到https://start.spring.io。該服務會拉取您的應用程式所需的所有依賴項,併為您完成大部分設定。
-
選擇 Gradle 或 Maven 以及您想使用的語言。本指南假設您選擇了 Java。
-
點選 Dependencies 並選擇 Spring Web。
-
點選 Generate。
-
下載生成的 ZIP 檔案,這是一個已根據您的選擇配置好的 Web 應用程式存檔。
如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此過程。 |
您還可以從 Github fork 該專案,並在您的 IDE 或其他編輯器中開啟它。 |
建立簡單的應用程式
為您的 Spring 應用程式建立一個新的控制器。以下列表(來自 src/main/java/com/example/testingweb/HomeController.java
)展示瞭如何操作
上述示例沒有區分 GET 、PUT 、POST 等。預設情況下,@RequestMapping 對映所有 HTTP 操作。您可以使用 @GetMapping 或 @RequestMapping(method=GET) 來縮小此對映範圍。 |
執行應用程式
Spring Initializr 會為您建立一個應用程式類(一個包含 main()
方法的類)。對於本指南,您無需修改此類。以下列表(來自 src/main/java/com/example/testingweb/TestingWebApplication.java
)展示了 Spring Initializr 建立的應用程式類
package com.example.testingweb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestingWebApplication {
public static void main(String[] args) {
SpringApplication.run(TestingWebApplication.class, args);
}
}
@SpringBootApplication
是一個便捷註解,包含了以下所有註解
-
@Configuration
:將類標記為應用程式上下文的 Bean 定義源。 -
@EnableAutoConfiguration
:告訴 Spring Boot 根據類路徑設定、其他 Bean 和各種屬性設定開始新增 Bean。 -
@EnableWebMvc
:將應用程式標記為 Web 應用程式並激活關鍵行為,例如設定DispatcherServlet
。當在類路徑中看到spring-webmvc
時,Spring Boot 會自動新增它。 -
@ComponentScan
:告訴 Spring 在您的帶註解的TestingWebApplication
類所在的包 (com.example.testingweb
) 中查詢其他元件、配置和服務,從而找到com.example.testingweb.HelloController
。
main()
方法使用 Spring Boot 的 SpringApplication.run()
方法來啟動應用程式。您注意到沒有一行 XML 程式碼嗎?也沒有 web.xml
檔案。這個 Web 應用程式是 100% 純 Java 的,您無需處理任何底層或基礎設施的配置。Spring Boot 會為您處理所有這些。
日誌輸出會顯示出來。服務應該會在幾秒鐘內啟動並執行。
測試應用程式
現在應用程式正在執行,您可以測試它了。您可以在 https://:8080
載入主頁。然而,為了讓您在進行更改時對應用程式的功能更有信心,您需要自動化測試過程。
Spring Boot 假設您計劃測試您的應用程式,因此它會將必要的依賴項新增到您的構建檔案(build.gradle 或 pom.xml )中。 |
您可以做的第一件事是編寫一個簡單的健全性檢查測試,如果應用程式上下文無法啟動,該測試將失敗。以下列表(來自 src/test/java/com/example/testingweb/TestingWebApplicationTest.java
)展示瞭如何操作
package com.example.testingweb;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TestingWebApplicationTests {
@Test
void contextLoads() {
}
}
@SpringBootTest
註解告訴 Spring Boot 查詢主配置類(例如帶有 @SpringBootApplication
的類),並使用它來啟動 Spring 應用程式上下文。您可以在 IDE 中或命令列中執行此測試(透過執行 ./mvnw test
或 ./gradlew test
),並且它應該會透過。為了讓您確信上下文正在建立您的控制器,您可以新增一個斷言,如下例所示(來自 src/test/java/com/example/testingweb/SmokeTest.java
)
package com.example.testingweb;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SmokeTest {
@Autowired
private HomeController controller;
@Test
void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Spring 會解析 @Autowired
註解,並在測試方法執行之前注入控制器。我們使用 AssertJ(它提供了 assertThat()
和其他方法)來表達測試斷言。
Spring Test 支援的一個很好的特性是應用程式上下文會在測試之間被快取。這樣,如果一個測試用例中有多個方法,或者有多個測試用例使用相同的配置,它們只需承擔一次啟動應用程式的成本。您可以使用 @DirtiesContext 註解來控制快取。 |
進行健全性檢查很好,但您還應該編寫一些測試來斷言應用程式的行為。為此,您可以啟動應用程式並監聽連線(就像在生產環境中那樣),然後傳送 HTTP 請求並斷言響應。以下列表(來自 src/test/java/com/example/testingweb/HttpRequestTest.java
)展示瞭如何操作
package com.example.testingweb;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.web.server.LocalServerPort;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("https://:" + port + "/",
String.class)).contains("Hello, World");
}
}
請注意使用 webEnvironment=RANDOM_PORT
以隨機埠啟動伺服器(這有助於避免測試環境中的衝突)以及使用 @LocalServerPort
注入埠。另外,請注意 Spring Boot 已自動為您提供了 TestRestTemplate
。您只需為其新增 @Autowired
即可。
另一個有用的方法是根本不啟動伺服器,而只測試其下面的層,即 Spring 處理傳入的 HTTP 請求並將其傳遞給您的控制器。這樣,幾乎使用了完整的技術棧,並且您的程式碼將以與處理真實 HTTP 請求完全相同的方式被呼叫,但無需啟動伺服器的成本。為此,請使用 Spring 的 MockMvc
,並透過在測試用例上使用 @AutoConfigureMockMvc
註解來請求將其注入。以下列表(來自 src/test/java/com/example/testingweb/TestingWebApplicationTest.java
)展示瞭如何操作
package com.example.testingweb;
import static org.hamcrest.Matchers.containsString;
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;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
class TestingWebApplicationTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")));
}
}
在此測試中,完整的 Spring 應用程式上下文已啟動,但不包括伺服器。我們可以透過使用 @WebMvcTest
將測試範圍縮小到僅 Web 層,如下列表(來自 src/test/java/com/example/testingweb/WebLayerTest.java
)所示
@WebMvcTest
include::complete/src/test/java/com/example/testingweb/WebLayerTest.java
測試斷言與前一個示例相同。然而,在此測試中,Spring Boot 僅例項化 Web 層而不是整個上下文。在一個包含多個控制器的應用程式中,您甚至可以透過使用例如 @WebMvcTest(HomeController.class)
來指定只例項化其中一個。
到目前為止,我們的 HomeController
很簡單,並且沒有依賴項。我們可以透過引入一個額外的元件來儲存問候語(可能在一個新的控制器中)使其更具真實性。以下示例(來自 src/main/java/com/example/testingweb/GreetingController.java
)展示瞭如何操作
package com.example.testingweb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class GreetingController {
private final GreetingService service;
public GreetingController(GreetingService service) {
this.service = service;
}
@RequestMapping("/greeting")
public @ResponseBody String greeting() {
return service.greet();
}
}
然後建立一個問候語服務,如下列表(來自 src/main/java/com/example/testingweb/GreetingService.java
)所示
package com.example.testingweb;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet() {
return "Hello, World";
}
}
Spring 會自動將服務依賴項注入到控制器中(由於建構函式簽名)。以下列表(來自 src/test/java/com/example/testingweb/WebMockTest.java
)展示瞭如何使用 @WebMvcTest
測試此控制器
package com.example.testingweb;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Mockito.when;
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;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(GreetingController.class)
class WebMockTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private GreetingService service;
@Test
void greetingShouldReturnMessageFromService() throws Exception {
when(service.greet()).thenReturn("Hello, Mock");
this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, Mock")));
}
}
我們使用 @MockBean
建立並注入 GreetingService
的 mock(如果您不這樣做,應用程式上下文將無法啟動),然後使用 Mockito
設定其期望。
總結
恭喜!您已經開發了一個 Spring 應用程式,並使用 JUnit 和 Spring MockMvc
對其進行了測試,還使用了 Spring Boot 來隔離 Web 層並載入一個特殊的應用程式上下文。
另請參閱
以下指南可能也有幫助
想撰寫新指南或貢獻現有指南?請檢視我們的貢獻指南。
所有指南的程式碼均採用 ASLv2 許可證釋出,文字內容採用 署名-禁止演繹 (Attribution, NoDerivatives) 知識共享許可證。 |