Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

下一篇[未完待續]

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

46.3、測試 Spring Boot 應用程序

Spring Boot 應用程序是 Spring ApplicationContext,因此除了通常使用普通的 Spring 上下文所做的事情之外,不必做任何特別的事情來測試它。

註釋:默認情況下,只有在使用 SpringApplication 創建 Spring Boot 時,後者的外部屬性、日誌記錄和其他功能才會安裝在上下文中。

Spring Boot 提供了一個 @SpringBootTest 註解,當你需要 Spring Boot 功能時,它可以作為標準 spring-test @ContextConfiguration 註解的替代。該註解的工作方式是通過 SpringApplication 創建測試中使用的 ApplicationContext。除了 @SpringBootTest 之外,還提供了許多其他註解來測試應用程序更具體的切片。

提示:如果你正在使用 JUnit 4,不要忘記在你的測試中添加 @RunWith(sprint grunner.class),否則註解將被忽略。如果你使用的是 JUnit 5,則無需添加等效的 @ExtendWith(SpringExtension.class) 作為 @SpringBootTest,其他 @… 測試註解已經用它註解了。

默認情況下,@SpringBootTest 不會啟動服務器。你可以使用 @SpringBootTest 的 webEnvironment 屬性來進一步優化測試的運行方式:

(1)MOCK(默認):加載 web ApplicationContext 並提供模擬 web 環境。使用此註解時,嵌入式服務器不會啟動。如果你的類路徑上沒有可用的 web 環境,則此模式將透明地返回到創建常規的非 web ApplicationContext。它可以與 @AutoConfigureMockMvc 或 @AutoConfigureWebTestClient 結合使用,用於 web 應用程序的基於模擬的測試。

(2)RANDOM_PORT:加載 WebServerApplicationContext 並提供一個真實的 web 環境。嵌入式服務器在隨機端口上啟動和監聽。

(3)DEFINED_PORT:加載 WebServerApplicationContext 並提供一個真實的 web 環境。嵌入式服務器在一個定義的端口(來自 application.properties)或默認端口 8080 上啟動和偵聽。

(4)NONE:通過使用 SpringApplication 加載 ApplicationContext,但不提供任何 web 環境(mock 或其他)。

註釋:如果你的測試是 @Transaction,則默認情況下,它會在每個測試方法結束時回滾事務。然而,由於對 RANDOM_PORT 或 DEFINED_PORT 使用這種安排隱式地提供了一個真正的 servlet 環境,所以 HTTP 客戶端和服務器在單獨的線程中運行,從而運行在單獨的事務中。在這種情況下,服務器上啟動的任何事務都不會回滾。

註釋:如果你的應用程序為管理服務器使用不同的端口,那麼使用 webEnvironment = WebEnvironment.RANDOM_PORT 的 @SpringBootTest 也將在一個單獨的隨機端口上啟動管理服務器。

46.3.1、檢測 Web 應用程序類型

如果 Spring MVC 可用,則配置一個常規的基於 MVC 的應用程序上下文。如果你只有 Spring WebFlux,我們將檢測它並配置一個基於 WebFlux 的應用程序上下文。

如果兩者都存在,則 Spring MVC 優先。如果要在這個場景中測試反應式 web 應用程序,則必須設置 spring.main.web-application-type 屬性:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
public class MyWebFluxTests { ... }/<code>

46.3.2、檢測測試配置

如果你熟悉 Spring Test Framework,那麼可能習慣於使用 @ContextConfiguration(classes=…) 來指定要加載哪個 Spring @Configuration。或者,你可能經常在測試中使用嵌套的 @Configuration 類。

在測試 Spring Boot 應用程序時,通常不需要這樣做。當你沒有明確定義主配置時,Spring Boot 的 @*Test 註解會自動搜索主配置。

搜索算法從包含測試的包開始工作,直到找到一個用 @SpringBootApplication 或 @SpringBootConfiguration 註解的類。只要你以合理的方式構建代碼,你的主要配置通常會被找到。

註解:

如果你使用測試註釋來測試應用程序的更具體的切片,則應避免在主方法的應用程序類上添加特定於特定區域的配置設置。

@SpringBootApplication 的底層組件掃描配置定義了用於確保切片按預期工作的排除過濾器。如果在基於註解 @SpringBootApplication 的類上使用顯式的 @ComponentScan 指令,請注意這些過濾器將被禁用。如果使用切片,則應重新定義它們。

如果要自定義主配置,可以使用嵌套的 @TestConfiguration 類。與嵌套的 @Configuration 類(將用於替代應用程序的主配置)不同,嵌套的 @TestConfiguration 類是在應用程序的主配置之外使用的。

註釋:Spring 的測試框架在測試之間緩存應用程序上下文。因此,只要你的測試共享相同的配置(無論如何發現),加載上下文的潛在耗時過程只會發生一次。

46.3.3、排除測試配置

如果你的應用程序使用組件掃描(例如,如果你使用 @SpringBootApplication 或 @ComponentScan),你可能會發現僅為特定測試創建的頂級配置類會意外地在任何地方出現。

如前所述,@TestConfiguration 可用於測試的內部類,以自定義主配置。當放置在頂級類上時,@TestConfiguration 表示 src/test/java 中的類不應該通過掃描來獲取。然後,可以在需要時顯式導入該類,如下例所示:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>@RunWith(SpringRunner.class)
@SpringBootTest
@Import(MyTestsConfiguration.class)
public class MyTests {

@Test
public void exampleTest() {
...
}

}/<code>

註釋:如果你直接使用 @ComponentScan (也就是說,不是通過 @SpringBootApplication),你需要用它註冊 TypeExcludeFilter。有關詳細信息,請參見 Javadoc。(https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/api/org/springframework/boot/context/TypeExcludeFilter.html )

46.3.4、使用模擬環境進行測試

默認情況下,@SpringBootTest 不啟動服務器。如果你有要針對此模擬環境進行測試的 web 端點,則可以另外配置 MockMvc,如以下示例所示:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>import org.junit.Test;
import org.junit.runner.RunWith;

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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {

@Autowired
private MockMvc mvc;

@Test
public void exampleTest() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}

}/<code>

提示:如果你只想關注 web 層而不啟動完整的 ApplicationContext,請考慮使用 @WebMvcTest。

或者,你可以配置 WebTestClient,如下面示例所示:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebTestClient
public class MockWebTestClientExampleTests {

@Autowired
private WebTestClient webClient;

@Test
public void exampleTest() {
this.webClient.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class)
.isEqualTo("Hello World");
}

}
/<code>

46.3.5、使用正在運行的服務器進行測試

如果你需要啟動完全運行的服務器,我們建議你使用隨機端口。如果使用 @SpringBootTest (webEnvironment=WebEnvironment.RANDOM_PORT),則每次測試運行時都會隨機選擇一個可用端口。

@LocalServerPort 註解可用於將實際使用的端口注入測試。為了方便起見,需要對啟動的服務器進行 REST 調用的測試還可以 @Autowire 一個 WebTestClient,它解析到正在運行的服務器的相關鏈接,並附帶用於驗證響應的專用 API,如下面示例所示:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>import org.junit.Test;
import org.junit.runner.RunWith;

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.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortWebTestClientExampleTests {

@Autowired
private WebTestClient webClient;

@Test
public void exampleTest() {
this.webClient.get().uri("/").exchange().expectStatus().isOk().expectBody(String.class)

.isEqualTo("Hello World");
}

}
/<code>

此設置需要類路徑上的 spring-webflux。如果你不能或不想添加 webflux,Spring Boot 還提供了一個 TestRestTemplate 工具:

Spring Boot中文參考指南 46.3、測試 Spring Boot 應用程序

<code>import org.junit.Test;
import org.junit.runner.RunWith;

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.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortTestRestTemplateExampleTests {

@Autowired
private TestRestTemplate restTemplate;

@Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}

}
/<code>

下一篇[未完待續]


分享到:


相關文章: