如何測(cè)試Spring MVC應(yīng)用
Spring的依賴注入使得我們的代碼非常容易進(jìn)行單元測(cè)試——@Controller
, @Service
,@Entity
等注解標(biāo)注的類基本都是POJO(plain old Java object),也就是說(shuō)很少依賴于Spring容器本身的API。我們可以非常容易地使用JUnit或TestNG編寫測(cè)試代碼。另一方面,對(duì)于三層架構(gòu)的Spring Web應(yīng)用(Controller, Service, DAO),使用Mock活Stub方法也能夠更好的來(lái)測(cè)試我們的代碼邏輯。例如Service層代碼的單元測(cè)試中,依賴的DAO(或Repository)對(duì)象都是根據(jù)應(yīng)用測(cè)試需求Mock出來(lái)的,而不需要真正去訪問(wèn)數(shù)據(jù)庫(kù)。
Spring Web測(cè)試
在對(duì)Spring Web應(yīng)用中的@Controller
代碼進(jìn)行單元測(cè)試的過(guò)程中,一般的方法是創(chuàng)建@Controller
對(duì)象,同時(shí)將它依賴的一些Mock對(duì)象——例如MockHttpServletRequest, MockHttpServletResponse(都由spring-test模塊提供,無(wú)需自己編寫)作為@Controller方法的參數(shù)。但是對(duì)于處理Web請(qǐng)求的@Controller代碼來(lái)說(shuō),僅僅測(cè)試Handler方法里的代碼是遠(yuǎn)遠(yuǎn)不夠的,對(duì)于一個(gè)處理HTTP請(qǐng)求的@Controller`,我們還需要測(cè)試:
@RequestMapping
路由是否正確- 數(shù)據(jù)綁定、類型轉(zhuǎn)換、校驗(yàn)邏輯是否正確——數(shù)據(jù)包括URL參數(shù)、表單、
@PathVariable
等 @InitBinder
,@ModelAttribute
,@ExceptionHandler
等注解的方法或?qū)傩杂?jì)算過(guò)程
上述過(guò)程貫穿于HTTP請(qǐng)求處理的生命周期中,所以對(duì)于Spring Web應(yīng)用中@Controller
代碼單元測(cè)試的概念,應(yīng)該做一些擴(kuò)充——不僅僅局限于代碼本身,也要結(jié)合MVC框架中的各個(gè)處理過(guò)程。
本文接下來(lái)的內(nèi)容代碼,都以Spring Boot為例,首先假設(shè)我們通過(guò)Spring Boot創(chuàng)建了一個(gè)最簡(jiǎn)單的Web Mvc應(yīng)用——包含了一個(gè)最簡(jiǎn)單的Conroller,處理/users/{id}
對(duì)應(yīng)的HTTP請(qǐng)求,返回值是id={id}
(通過(guò)String.format()
方法),那么可以為它創(chuàng)建如下測(cè)試代碼:
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(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SpringMvcTestDemoApplication.class) @WebAppConfiguration public class SpringMvcTestDemoApplicationTests { private MockMvc mockMvc; @Before public void init() { this.mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build(); } @Test public void getUserById() throws Exception { long id = 1; this.mockMvc.perform(get("/users/" + id)) .andExpect(status().isOk()) .andExpect(content().string("id=" + id)); } }
運(yùn)行上述測(cè)試時(shí),很容易從控制臺(tái)中的日志發(fā)現(xiàn),SpringJUnit4ClassRunner
創(chuàng)建了一個(gè)Spring Web應(yīng)用上下文,并且在其中進(jìn)行了Web Mvc框架的配置——這里是注冊(cè)@RequestMapping
方法。接下來(lái)mockMvc.perform()
方法實(shí)際上向該Spring Web應(yīng)用發(fā)起了一個(gè)HTTP請(qǐng)求:
- 請(qǐng)求的url為
/users/{id}
andExpect()
方法也就是測(cè)試中常用的Assert
status()
用于檢查返回狀態(tài)嗎,這里是200content()
用于檢查內(nèi)容
如果我們不小心將@RequestMapping
的路由路徑寫錯(cuò),那么這里運(yùn)行的結(jié)果一定不會(huì)是status().isOk()
,這也就完成了對(duì)HTTP請(qǐng)求路由的測(cè)試。接下來(lái)我們將繼續(xù)探索MVC框架中的其他方面。
Mock Service
在Spring Web應(yīng)用三層結(jié)構(gòu)里,Controller層代碼通常會(huì)調(diào)用Service層代碼,例如:
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/users/{id}", method = GET) public String get(@PathVariable("id") long id) { String username = userService.getUsername(id); return String.format("username=%s", username); } }
對(duì)UserController
進(jìn)行單元測(cè)試需要排除Service代碼的影響,所以需要對(duì)Service進(jìn)行Mock,這里我們使用Mockito框架,在Spring上下文中Mock一個(gè)UserService
對(duì)象:
@Configuration public class TestContext { @Bean public UserService userServiceMock() { return Mockito.mock(UserService.class); } }
同時(shí)通過(guò)Mockito
的API來(lái)MockUserService.getUsername(long id)
方法,@Controller
的測(cè)試代碼如下:
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { SpringMvcTestDemoApplication.class, TestContext.class }) @WebAppConfiguration public class SpringMvcTestDemoApplicationTests { @Autowired UserService userService; @Autowired UserController controller; MockMvc mockMvc; @Before public void init() { this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); } @Test public void getUserById() throws Exception { long id = 1L; String ricky = "Ricky"; Mockito.when(userService.getUsername(id)).thenReturn(ricky); this.mockMvc.perform(get("/users/" + id)) .andExpect(status().isOk()) .andExpect(content().string("username=" + ricky)); } }
由于需要進(jìn)行依賴注入,所以UserService
和UserController
都使用@Autowired
注解。Mockito.when(userService.getUsername(id)).thenReturn(ricky)
;表明userService.getUsername()
方法的參數(shù)為1L時(shí),返回值為"Ricky
",Mockito提供能很多強(qiáng)大的Mock API,更多用法請(qǐng)參考官方文檔。
測(cè)試REST API
當(dāng)我們構(gòu)建REST服務(wù)時(shí),大多數(shù)情況會(huì)使用JSON作為數(shù)據(jù)交換格式,Spring MVC測(cè)試框架同樣提供了一種簡(jiǎn)潔的方式對(duì)JSON結(jié)果進(jìn)行斷言,假設(shè)現(xiàn)在有@Controller
如下:
@RequestMapping(value = "/users/{id}/json", method = GET) public User getUser(@PathVariable("id") long id) { String username = userService.getUsername(id); return new User(id, username); } static class User { public long id; public String username; //構(gòu)造方法,Getter/Setter略 }
實(shí)際應(yīng)用返回的JSON數(shù)據(jù)是:
{ "id": 1, "username": "Ricky" }
測(cè)試代碼可以這樣斷言:
@Test public void getUser() throws Exception { this.mockMvc.perform(get("/users/{id}/json", id).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(id.intValue())) .andExpect(jsonPath("$.username").value(ricky)); }
$.id
, $.username
都是JsonPath提供的JSON表達(dá)式,可以通過(guò)jsonPath
、value()
等方法來(lái)輕松對(duì)JSON數(shù)據(jù)進(jìn)行斷言而不需要自己編寫JSON文本處理。
以上就是如何測(cè)試Spring MVC應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于測(cè)試Spring MVC應(yīng)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringMVC Mock測(cè)試實(shí)現(xiàn)原理及實(shí)現(xiàn)過(guò)程詳解
- SpringBoot MockMvc單元測(cè)試的示例代碼
- spring-mvc/springboot使用MockMvc對(duì)controller進(jìn)行測(cè)試
- postman+json+springmvc測(cè)試批量添加實(shí)例
- 詳解Spring MVC如何測(cè)試Controller(使用springmvc mock測(cè)試)
- Spring MVC全局異常處理和單元測(cè)試_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
相關(guān)文章
最新SpringCloud?Stream消息驅(qū)動(dòng)講解
SpringCloud Stream 是一個(gè)構(gòu)建消息驅(qū)動(dòng)微服務(wù)的框架,通過(guò) SpringCloud Stream 連接消息中間件,以實(shí)現(xiàn)消息事件驅(qū)動(dòng),這篇文章主要介紹了SpringCloud?Stream消息驅(qū)動(dòng),需要的朋友可以參考下2022-11-11springboot集成redis并使用redis生成全局唯一索引ID
本文主要介紹了springboot集成redis并使用redis生成全局唯一索引ID,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Java實(shí)現(xiàn)JSP在Servelt中連接Oracle數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Java實(shí)現(xiàn)JSP在Servelt中連接Oracle數(shù)據(jù)庫(kù)的方法,需要的朋友可以參考下2014-07-07SpringBoot 2.6.x整合springfox 3.0報(bào)錯(cuò)問(wèn)題及解決方案
這篇文章主要介紹了SpringBoot 2.6.x整合springfox 3.0報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Jmeter內(nèi)置變量vars和props的使用詳解
JMeter是一個(gè)功能強(qiáng)大的負(fù)載測(cè)試工具,它提供了許多有用的內(nèi)置變量來(lái)支持測(cè)試過(guò)程,其中最常用的變量是 vars 和 props,本文通過(guò)代碼示例詳細(xì)給大家介紹了Jmeter內(nèi)置變量vars和props的使用,需要的朋友可以參考下2024-08-08Mybatis返回值(resultType&resultMap)的具體使用
返回值屬性有兩種設(shè)置,一種是resultType,一種是resultMap,本文主要介紹了Mybatis返回值(resultType&resultMap)的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08Java技術(shù)長(zhǎng)久占居主要地位的12個(gè)原因
這篇文章主要為大家詳細(xì)介紹了12個(gè)Java長(zhǎng)久占居主要地位的原因,感興趣的小伙伴們可以參考一下2016-07-07寫了兩年代碼之后再來(lái)談一談Spring中的Bean
這篇文章主要介紹了寫了兩年代碼之后再來(lái)看看Spring中的Bean,這里列出四種常用的添加Bean的方式,介紹最基本的@Bean注解,@Bean注解聲明這個(gè)類是一個(gè)Bean,需要的朋友可以參考下2021-10-10SpringBoot中實(shí)現(xiàn)文件上傳、下載、刪除功能的步驟
本文將詳細(xì)介紹如何在 Spring Boot 中實(shí)現(xiàn)文件上傳、下載、刪除功能,采用的技術(shù)框架包括:Spring Boot 2.4.2、Spring MVC、MyBatis 3.5.6、Druid 數(shù)據(jù)源、JUnit 5 等,文中有詳細(xì)的操作步驟和示例代碼供大家參考,需要的朋友可以參考下2024-01-01