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
-
cd 進入
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 分叉專案,並在您的 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 Boot 在類別路徑上看到spring-webmvc
時,它會自動新增它。 -
@ComponentScan
:告知 Spring 在您的已註釋TestingWebApplication
類別所在的套件 (com.example.testingweb
) 中尋找其他元件、組態和服務,從而使其找到com.example.testingweb.HelloController
。
main()
方法使用 Spring Boot 的 SpringApplication.run()
方法來啟動應用程式。 您是否注意到沒有任何 XML 程式碼? 也沒有 web.xml
檔案。 這個 Web 應用程式是 100% 純 Java,您不必處理任何配置或基礎結構。 Spring Boot 會為您處理所有這些。
會顯示記錄輸出。 該服務應在幾秒鐘內啟動並執行。
測試應用程式
現在應用程式正在執行,您可以對其進行測試。 您可以在 https://127.0.0.1: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://127.0.0.1:" + 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
的模擬物件 (如果您不這樣做,應用程式內容將無法啟動),並且我們使用 Mockito
設定其預期行為。
總結
恭喜! 您已經開發了一個 Spring 應用程式,並使用 JUnit 和 Spring MockMvc
對其進行了測試,並且使用了 Spring Boot 來隔離 Web 層並載入特殊的應用程式內容。