@WebMvcTest測MVCWebContorller 一

原文 https://reflectoring。io/spring-boot-web-controller-test/

翻譯: 祝坤榮

@WebMvcTest測MVCWebContorller 一

在這個測試Spring Boot系列的第二部分,我們來看下web contoller。開始,我們會探索下web controller到底做了什麼,然後我們可以基於寫單元測試來覆蓋所有它的職責。

然後,我們來看看如果在測試用覆蓋這些職責。只有當所有這些職責都被覆蓋到了,我們才可以肯定我們的contoller的行為應該與線上環境一樣了。

樣例程式碼

這篇文章提供在

GitHub[1]

上的可執行程式碼。

測試Spring Boot系列

這篇教程是一個系列的一部分:

1。

Spring Boot的單元測試[2]

2。

使用@WebMvcTest測試Spring Boot的MVC Web Controller[3]

3。

使用@DataJpaTest測試Spring Boot的JPA Queries[4]

4。

使用@SpringBootTest進行整合測試[5]

如果你喜歡看影片學習,可以看看Philip的

測試Spring Boot應用課程[6]

(如果你透過這個連結購買,我有分成)。

依賴

我們會使用JUnit Jupiter(JUnit 5)作為測試框架,Mockito來模擬,AssertJ來建立斷言,Lombok來減少冗餘程式碼:

dependencies { compile(‘org。springframework。boot:spring-boot-starter-web’) compileOnly(‘org。projectlombok:lombok’) testCompile(‘org。springframework。boot:spring-boot-starter-test’) testCompile ‘org。junit。jupiter:junit-jupiter-engine:5。2。0’ testCompile(‘org。mockito:mockito-junit-jupiter:2。23。0’)}

AssertJ和Mockito會透過引入spring-boot-starter-test自動引入。

Web Controller的職責

讓我們先看一個典型的REST controller:

@RequiredArgsConstructorclass RegisterRestController { private final RegisterUseCase registerUseCase; @PostMapping(“/forums/{forumId}/register”) UserResource register( @PathVariable(“forumId”) Long forumId, @Valid @RequestBody UserResource userResource, @RequestParam(“sendWelcomeMail”) boolean sendWelcomeMail) { User user = new User( userResource。getName(), userResource。getEmail()); Long userId = registerUseCase。registerUser(user, sendWelcomeMail); return new UserResource( userId, user。getName(), user。getEmail()); }}

Controller的方法透過@PostMapping的宣告來定義需要監聽的URL,HTTP方法和content型別。

它接受透過@PathVariable, @RequestBody和@RequestsParam宣告的入參,其被進入的HTTP請求自動填充。

引數可能被宣告成@Valid來標明Spring需要對它們進行

bean校驗[7]

然後controller使用這些引數,呼叫業務邏輯,得到一個簡單Java物件,其會被以JSON的形式預設自動寫入到HTTP響應body中。

這裡有很多Spring魔法。簡單來說,對每一個請求,controller通常經過以下步驟:

#

職責

描述

1。

監聽HTTP請求

controller需要對特定的URL,HTTP方法和content型別做響應

2。

反序列化輸入

controller需要解析進入的HTTP請求並從URL,HTTP請求引數和請求body中建立Java物件,這樣我們在程式碼中使用

3。

檢查輸入

controller是防禦不合法輸入的第一道防線,所以這是個校驗輸入的好地方

4。

呼叫業務邏輯

得到了解析過的入參,controller需要將入參傳給業務邏輯期望的業務模型

5。

序列化輸出

controller得到業務邏輯的輸出並將其序列化到HTTP響應中

6。

翻譯異常

如果某些地方有異常發生了,controller需要將其翻譯成一個合理的錯誤訊息和HTTP狀態碼

所以controller有一大堆活要幹! 我們要注意不要再

加更多的像執行業務邏輯這樣的職責了。那樣的話,我們的controller測試會過於冗餘並難以維護。

我們如果編寫可以覆蓋所有這些職責的合理測試呢?

單元或整合測試?

我們是寫單元測試?還是寫整合測試呢?這兩個有什麼不同?讓我們看看兩種方式並選擇其中一個。

在單元測試中,我們需要將controller隔離測試。這表示我們要初始化一個controller物件,對

業務邏輯進行模擬[8]

,然後呼叫controller的方法並校驗返回。

這在我們的例子裡行嗎?讓我們看下在上面我們定義的6個職責能否在隔離的單元測試中覆蓋:

#

職責

描述

1。

監聽HTTP請求

不行,因為單元測試不會檢查@PostMapping宣告並模擬HTTP請求的特定引數

2。

反序列化輸入

不行,因為像@RequestParam和@pathVariable這樣的宣告不會被檢驗。我們會以Java物件的形式提供輸入,這會跳過HTTP請求的反序列化

3。

檢查輸入

不行,不依賴bean校驗,因為@Valid宣告不會被校驗。

4。

呼叫業務邏輯

可以,因為我們可以校驗業務邏輯被期望的引數呼叫

5。

序列化輸出

不行,因為只能校驗Java版本的輸出,HTTP返回不會生成

6。

翻譯異常

不行,我們可以檢查一個特定的異常是否產生,但它不會被翻譯成一個JSON返回或HTTP狀態碼

簡單來說,一個簡單的單元測試不能覆蓋HTTP層。所以我們要將Spring引入到測試中來幫我們做點HTTP魔法。因此,我們構建一個整合測試來測試我們controller程式碼與Spring提供的HTTP支援元件的整合。

一個Spring整合測試啟動一個Spring包含所有我們需要bean的應用上下文。這包括了負責監聽特定URL,序列化與反序列化JSON並翻譯HTTP異常的框架bean。這些bean會檢查在一個簡單的單元測試裡會被忽略的宣告。

那麼,我們怎麼做呢?

使用@WebMvcTest校驗Controller的職責

Spring Boot提供了@WebMvcTest宣告來載入只包括了需要測試web controller的bean的應用上下文:

@ExtendWith(SpringExtension。class)@WebMvcTest(controllers = RegisterRestController。class)class RegisterRestControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private RegisterUseCase registerUseCase; @Test void whenValidInput_thenReturns200() throws Exception { mockMvc。perform(。。。); }}

@ExtendWith這篇教程的程式碼樣例使用了@ExtendWith宣告來告訴JUnit 5來開啟Spring支援。 在Spring Boot 2。1,我們不再需要載入SpringExtension了,因為它已經被包含在像@DataJpaTest,@WebMvcTest和@SpringBootTest這樣的Spring Boot測試宣告中了。

現在我們可以在所有我們在應用上下文中需要的bean上使用@Autowire了。 Spring Boot會自動提供像@ObjectMapping這樣的bean來做對映並從JSON和MockMvc例項來模擬HTTP請求。

我們使用@MockBean來模擬業務邏輯,因為我們並不想測試controller與業務邏輯的整合,而只是要測試controller與Http層的整合。 @MockBean自動用Mockito的mock來替換應用上下文與被替換的bean同類型的bean。

你可以在我講

述模擬的文章[9]

來看更多關於@MockBean的內容。

使用@WebMvcTest在上例中透過將controller的引數設定到RegisterRestController。class上,我們告訴Spring Boot在建立上下文時限制給定的controller和一些Spring Web MVC框架的bean。而其他我們可能需要的bean被@MockBean隔離或模擬掉了。如果我們不傳controllers引數,Spring Boot會載入應用上下文中的所有controller。 這樣我們就需要載入或模擬每個controller依賴的所有bean。這回事測試的配置變得複雜的多,但由於所有的controller測試都可以重用相同的應用上下線而節省了時間。我打算將應用上下文縮小來限制controller測試,這樣可以讓測試保持獨立,不需要引入其他的bean,儘管這樣會讓Spring Boot在每次單個測試時都會建一個新的應用上下文。讓我們看一下每個職責,並看看如果透過使用MockMvc來校驗每項職責來進行最佳的整合測試。

插入一條推薦內容,我與其他2位作者一起翻譯的Spring 5設計模式21年2月已經在京東等電商渠道上架了,本書主要討論了在Spring框架中使用的經典設計模式,能幫助開發者瞭解Spring的內部原理,是一本不錯的學習書籍。

@WebMvcTest測MVCWebContorller 一

https://item。jd。com/10026604593899。html#none

本文來自祝坤榮(時序)的微信公眾號「麥芽麵包,id「darkjune_think」 轉載請註明。

開發者/科幻愛好者/硬核主機玩家/業餘翻譯 微博:祝坤榮 B站: https://space。bilibili。com/23185593/ 交流Email:

zhukunrong@yeah。net[10]

References

[1] GitHub:

https://github。com/thombergs/code-examples/tree/master/spring-boot/spring-boot-testing

[2] Spring Boot的單元測試:

https://reflectoring。io/unit-testing-spring-boot/

[3] 使用@WebMvcTest測試Spring Boot的MVC Web Controller:

https://reflectoring。io/spring-boot-web-controller-test/

[4] 使用@DataJpaTest測試Spring Boot的JPA Queries:

https://reflectoring。io/spring-boot-data-jpa-test/

[5] 使用@SpringBootTest進行整合測試:

https://reflectoring。io/spring-boot-test/

[6] 測試Spring Boot應用課程:

https://transactions。sendowl。com/stores/13745/194393

[7] bean校驗:

https://reflectoring。io/bean-validation-with-spring-boot/

[8] 業務邏輯進行模擬:

https://reflectoring。io/unit-testing-spring-boot/#using-mockito-to-mock-dependencies

[9] 述模擬的文章:

https://reflectoring。io/spring-boot-mock/

[10] zhukunrong@yeah。net:

mailto:zhukunrong@yeah。net