Spring入門(十四):Spring MVC控制器的2種測試方法
- 2019 年 10 月 3 日
- 筆記
作為一名研發人員,不管你願不願意對自己的代碼進行測試,都得承認測試對於研發質量保證的重要性,這也就是為什麼每個公司的技術部都需要質量控制部的原因,因為越早的發現代碼的bug,成本越低,比如說,Dev環境發現bug的成本要低於QA環境,QA環境發現bug的成本要低於Prod環境,Prod環境發現bug的成本最高,這也是每個研發人員最不願意遇到但永遠避不掉的現實。
雖然不能完全避免,但我們可以對自己的代碼進行充分的測試,降低bug出現的幾率。
所以, 本篇博客我們主要講解下Spring MVC控制器的2種測試方法:
- 部署項目後測試
- 藉助JUnit和Spring Test框架測試
1. 部署項目後測試
在前2篇博客中,我們採取的就是這種測試方式,即將項目打成war包,部署到Tomcat中,運行項目後, 藉助瀏覽器或者Postman等工具對控制器進行測試。
如果是get請求,可以使用瀏覽器或者Postman測試。
如果是post、put、delete等請求,可以使用Postman進行測試。
有興趣的同學,可以看下之前的2篇博客:
2. 藉助Junit和Spring Test框架測試
上面的方法雖然可以進行測試,但每次都打包、部署、運行項目、測試,顯然很不方便,不過強大的Spring通過Spring Test框架對集成測試提供了支持,接下來我們講解具體的使用方法。
因為我們的Spring項目是通過maven管理的,所以它的項目結構有以下4個目錄:
- src/main/java:項目代碼
- src/main/resources:項目資源
- src/test/java:測試代碼
- src/test/resources:測試資源(該目錄默認沒有生成,有需要的可以自己新建)
也就是說,我們可以將我們的測試代碼放在src/test/java目錄下,不過截止目前,我們還並未在該目錄添加任何測試代碼。
2.1 添加依賴
在添加測試代碼前,我們需要在pom.xml中添加如下依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.18.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
也許有的同學會好奇,為啥本次添加的依賴增加了<scope>test</scope>
, 它有啥作用呢?
帶着這個疑問,我們編譯下項目,發現原本編譯正常的代碼竟然編譯報錯了:
報錯信息提示程序包org.junit不存在,可我們明明添加了該依賴啊,這是為什麼呢,會不會和<scope>test</scope>
有關呢?
恭喜你,猜對了,確實和<scope>test</scope>
有關,如果你此時將該項移除,項目編譯就不報錯了(不過建議不要移除)。
這是因為,我們在之前添加測試代碼時,都是放在src/main/java目錄下的,現在依賴包增加了<scope>test</scope>
,說明這些包的存活周期是在test周期,所以我們可以把之前的測試代碼移到src/test/java目錄下,如下所示:
再次編譯項目,發現編譯通過。
2.2 添加控制器
添加控制器前,新建DemoService如下所示:
package chapter05.service; import org.springframework.stereotype.Service; @Service public class DemoService { public String saySomething() { return "hello"; } }
注意事項:該類添加了@Service註解。
然後,新建控制器NormalController,它裏面的方法返回jsp視圖:
package chapter05.controller; import chapter05.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class NormalController { @Autowired private DemoService demoService; @RequestMapping("/normal") public String testPage(Model model) { model.addAttribute("msg", demoService.saySomething()); return "page"; } }
接着新建控制器MyRestController,它裏面的方法直接返回信息:
package chapter05.controller; import chapter05.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MyRestController { @Autowired private DemoService demoService; @RequestMapping(value = "/testRest", produces = "text/plain;charset=UTF-8") public String testRest() { return demoService.saySomething(); } }
2.3 添加測試代碼
在src/test/java下新建包chapter05,然後在其下面新建測試類TestControllerIntegrationTests如下所示:
package chapter05; import chapter05.config.MyMvcConfig; import chapter05.service.DemoService; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {MyMvcConfig.class}) @WebAppConfiguration("src/main/resources") public class TestControllerIntegrationTests { private MockMvc mockMvc; @Autowired private DemoService demoService; @Autowired private WebApplicationContext webApplicationContext; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); } }
代碼講解:
@RunWith(SpringJUnit4ClassRunner.class)
用於在JUnit環境下提供Spring Test框架的功能。
@ContextConfiguration(classes = {MyMvcConfig.class})
用來加載配置ApplicationContext,其中classes屬性用來加載配置類,MyMvcConfig配置類的代碼如下所示:
package chapter05.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; /** * Spring MVC配置 */ @Configuration @EnableWebMvc @ComponentScan("chapter05") public class MyMvcConfig { /** * 視圖解析器配置 * * @return */ @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/classes/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; } }
@WebAppConfiguration("src/main/resources")
用來聲明加載的ApplicationContext是一個WebApplicationContext,它的屬性指定的是Web資源的位置,默認為src/main/webapp,這裡我們修改成
src/main/resources。
MockMvc用來模擬Mvc對象,它在添加了@Before
註解的setup()中,通過this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
進行初始化賦值。
然後往測試類中添加如下測試代碼:
@Test public void testNormalController() throws Exception { mockMvc.perform(get("/normal")) .andExpect(status().isOk()) .andExpect(view().name("page")) .andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp")) .andExpect(model().attribute("msg", demoService.saySomething())); }
代碼解釋:
perform(get("/normal"))
用來模擬向/normal發起get請求,
andExpect(status().isOk())
預期返回的狀態碼為200,
andExpect(view().name("page"))
預期視圖的邏輯名稱為page,
andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))
預期視圖的真正路徑是/WEB-INF/classes/views/page.jsp",
andExpect(model().attribute("msg", demoService.saySomething()))
預期Model里有一個msg屬性,它的值是demoService.saySomething()
的返回值hello。
執行該測試方法,測試通過:
最後往測試類中添加如下測試代碼:
@Test public void testRestController() throws Exception { mockMvc.perform(get("/testRest")) .andExpect(status().isOk()) .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().string(demoService.saySomething())); }
代碼解釋:
perform(get("/testRest"))
用來模擬向/testRest發起get請求,
andExpect(status().isOk())
預期返回的狀態碼為200,
andExpect(content().contentType("text/plain;charset=UTF-8"))
預期返回值的媒體類型為text/plain;charset=UTF-8,
andExpect(content().string(demoService.saySomething()))
預期返回值的內容為demoService.saySomething()
的返回值hello。
執行該測試方法,測試通過:
3. 源碼及參考
源碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。
Craig Walls 《Spring實戰(第4版)》
汪雲飛《Java EE開發的顛覆者:Spring Boot實戰》
4. 最後
歡迎掃碼關注微信公眾號:「申城異鄉人」,定期分享Java技術乾貨,讓我們一起進步。