隨著RESTful Web Service的流行,測試對外的Service是否滿足期望也變的必要的。從Spring 3.2開始Spring了Spring Web測試框架,如果版本低于3.2,請使用 spring-test-mvc 項目(合并到spring3.2中了)。
?
Spring MVC測試框架提供了對服務器端和客戶端(基于RestTemplate的客戶端)提供了支持。
?
對于服務器端:在Spring 3.2之前,我們測試時一般都是直接new控制器,注入依賴,然后判斷返回值。但是我們無法連同Spring MVC的基礎設施(如DispatcherServlet調度、類型轉換、數據綁定、攔截器等)一起測試,另外也沒有現成的方法測試如最終渲染的視圖(@ResponseBody生成的JSON/XML、JSP、Velocity等)內容是否正確。從Spring 3.2開始這些事情都可以完成了。而且可以測試完整的Spring MVC流程,即從URL請求到控制器處理,再到視圖渲染都可以測試。
?
對于客戶端:不需要啟動服務器即可測試我們的RESTful 服務。
?
1 服務器端測試
我的環境:JDK7、Maven3、spring4、Servlet3
?
首先添加依賴
如下是spring-context和spring-webmvc依賴:
- <dependency>??
- ????<groupId>org.springframework</groupId>??
- ????<artifactId>spring-context</artifactId>??
- ????<version>${spring.version}</version>??
- </dependency>??
- ??
- <dependency>??
- ????<groupId>org.springframework</groupId>??
- ????<artifactId>spring-webmvc</artifactId>??
- ????<version>${spring.version}</version>??
- </dependency>??
版本信息:<spring.version>4.0.0.RELEASE</spring.version>
?
如下是測試相關的依賴(junit、hamcrest、mockito、spring-test):
- <dependency>??
- ????<groupId>junit</groupId>??
- ????<artifactId>junit</artifactId>??
- ????<version>${junit.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
- ??
- <dependency>??
- ????<groupId>org.hamcrest</groupId>??
- ????<artifactId>hamcrest-core</artifactId>??
- ????<version>${hamcrest.core.version}/version>??
- ????<scope>test</scope>??
- </dependency>??
- <dependency>??
- ????<groupId>org.mockito</groupId>??
- ????<artifactId>mockito-core</artifactId>??
- ????<version>${mockito.core.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
- ??
- <dependency>??
- ????<groupId>org.springframework</groupId>??
- ????<artifactId>spring-test</artifactId>??
- ????<version>${spring.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
然后準備測試相關配置
實體:
- package ?com.sishuok.mvc.entity;??
- import ?java.io.Serializable;??
- public ? class ?User? implements ?Serializable?{??
- ???? private ?Long?id;??
- ???? private ?String?name;??
- ???? //省略getter/setter等 ??
- }??
?
控制器:
- package ?com.sishuok.mvc.controller;??
- //省略import ??
- @Controller ??
- @RequestMapping ( "/user" )??
- public ? class ?UserController?{??
- ??
- ???? @RequestMapping ( "/{id}" )??
- ???? public ?ModelAndView?view( @PathVariable ( "id" )?Long?id,?HttpServletRequest?req)?{??
- ????????User?user?=? new ?User();??
- ????????user.setId(id);??
- ????????user.setName( "zhang" );??
- ??
- ????????ModelAndView?mv?=? new ?ModelAndView();??
- ????????mv.addObject( "user" ,?user);??
- ????????mv.setViewName( "user/view" );??
- ???????? return ?mv;??
- ????}??
- }??
?
XML風格配置:
spring-config.xml :加載非web層組件 ?
- <?xml?version= "1.0" ?encoding= "UTF-8" ?>??
- <beans?xmlns= "http://www.springframework.org/schema/beans" ??
- ???????xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" ??
- ???????xmlns:context= "http://www.springframework.org/schema/context" ??
- ???????xsi:schemaLocation="??
- ???????http: //www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd ??
- ???????http: //www.springframework.org/schema/context?http://www.springframework.org/schema/context/spring-context.xsd ??
- ???????">??
- ????<!--?通過web.xml中的?org.springframework.web.context.ContextLoaderListener?加載的??-->??
- ????<!--?請參考?http: //jinnianshilongnian.iteye.com/blog/1602617??--> ??
- ????<context:component-scan?base- package = "com.sishuok.mvc" >??
- ????????<context:exclude-filter?type= "annotation" ?expression= "org.springframework.stereotype.Controller" />??
- ????</context:component-scan>??
- </beans>??
?
spring-mvc.xml :加載和配置web層組件 ?
- <?xml?version= "1.0" ?encoding= "UTF-8" ?>??
- <beans?xmlns= "http://www.springframework.org/schema/beans" ??
- ???????xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" ??
- ???????xmlns:context= "http://www.springframework.org/schema/context" ??
- ???????xmlns:mvc= "http://www.springframework.org/schema/mvc" ??
- ???????xsi:schemaLocation="??
- ???????http: //www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd ??
- ???????http: //www.springframework.org/schema/context?http://www.springframework.org/schema/context/spring-context.xsd ??
- ???????http: //www.springframework.org/schema/mvc?http://www.springframework.org/schema/mvc/spring-mvc.xsd ??
- ???????">??
- ????<!--?通過web.xml中的?org.springframework.web.servlet.DispatcherServlet?加載的??-->??
- ????<!--?請參考?http: //jinnianshilongnian.iteye.com/blog/1602617??--> ??
- ????<context:component-scan?base- package = "com.sishuok.mvc" ?use- default -filters= "false" >??
- ????????<context:include-filter?type= "annotation" ?expression= "org.springframework.stereotype.Controller" />??
- ????</context:component-scan>??
- ????<mvc:annotation-driven/>??
- ????<bean?id= "viewResolver" ? class = "org.springframework.web.servlet.view.InternalResourceViewResolver" >??
- ????????<property?name= "prefix" ?value= "/WEB-INF/jsp/" />??
- ????????<property?name= "suffix" ?value= ".jsp" />??
- ????</bean>??
- </beans>??
?
web.xml配置 :此處就不貼了,請前往github查看。
?
對于context:component-scan注意事項請參考《 context:component-scan掃描使用上的容易忽略的use-default-filters 》和《 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC 》。
?
等價的注解風格配置: ?
AppConfig.java :等價于spring-config.xml
- package ?com.sishuok.config;??
- ??
- import ?org.springframework.context.annotation.ComponentScan;??
- import ?org.springframework.context.annotation.Configuration;??
- import ?org.springframework.context.annotation.FilterType;??
- import ?org.springframework.stereotype.Controller;??
- ??
- @Configuration ??
- @ComponentScan (basePackages?=? "com.sishuok.mvc" ,?excludeFilters?=?{??
- ???????? @ComponentScan .Filter(type?=?FilterType.ANNOTATION,?value?=?{Controller. class })??
- })??
- public ? class ?AppConfig?{??
- }??
?
MvcConfig.java :等價于spring-mvc.xml
- package ?com.sishuok.config;??
- ??
- import ?org.springframework.context.annotation.Bean;??
- import ?org.springframework.context.annotation.ComponentScan;??
- import ?org.springframework.context.annotation.Configuration;??
- import ?org.springframework.context.annotation.FilterType;??
- import ?org.springframework.stereotype.Controller;??
- import ?org.springframework.web.servlet.ViewResolver;??
- import ?org.springframework.web.servlet.config.annotation.EnableWebMvc;??
- import ?org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;??
- import ?org.springframework.web.servlet.view.InternalResourceViewResolver;??
- ??
- @Configuration ??
- @EnableWebMvc ??
- @ComponentScan (basePackages?=? "com.sishuok.mvc" ,?useDefaultFilters?=? false ,?includeFilters?=?{??
- ???????? @ComponentScan .Filter(type?=?FilterType.ANNOTATION,?value?=?{Controller. class })??
- })??
- public ? class ?MvcConfig? extends ?WebMvcConfigurationSupport?{??
- ??
- ???? @Bean ??
- ???? public ?ViewResolver?viewResolver()?{??
- ????????InternalResourceViewResolver?viewResolver?=? new ?InternalResourceViewResolver();??
- ????????viewResolver.setPrefix( "/WEB-INF/jsp/" );??
- ????????viewResolver.setSuffix( ".jsp" );??
- ???????? return ?viewResolver;??
- ????}??
- ??
- }??
WebInitializer.java :注冊相應的web.xml中的組件
- package ?com.sishuok.config;??
- ??
- import ?org.springframework.web.WebApplicationInitializer;??
- import ?org.springframework.web.context.ContextLoaderListener;??
- import ?org.springframework.web.context.support.AnnotationConfigWebApplicationContext;??
- import ?org.springframework.web.filter.CharacterEncodingFilter;??
- import ?org.springframework.web.servlet.DispatcherServlet;??
- ??
- import ?javax.servlet.DispatcherType;??
- import ?javax.servlet.FilterRegistration;??
- import ?javax.servlet.ServletException;??
- import ?javax.servlet.ServletRegistration;??
- import ?java.util.EnumSet;??
- ??
- public ? class ?WebInitializer? implements ?WebApplicationInitializer?{??
- ??
- ???? @Override ??
- ???? public ? void ?onStartup(javax.servlet.ServletContext?sc)? throws ?ServletException?{??
- ??
- ????????AnnotationConfigWebApplicationContext?rootContext?=? new ?AnnotationConfigWebApplicationContext();??
- ????????rootContext.register(AppConfig. class );??
- ????????sc.addListener( new ?ContextLoaderListener(rootContext));??
- ??
- ???????? //2、springmvc上下文 ??
- ????????AnnotationConfigWebApplicationContext?springMvcContext?=? new ?AnnotationConfigWebApplicationContext();??
- ????????springMvcContext.register(MvcConfig. class );??
- ???????? //3、DispatcherServlet ??
- ????????DispatcherServlet?dispatcherServlet?=? new ?DispatcherServlet(springMvcContext);??
- ????????ServletRegistration.Dynamic?dynamic?=?sc.addServlet( "dispatcherServlet" ,?dispatcherServlet);??
- ????????dynamic.setLoadOnStartup( 1 );??
- ????????dynamic.addMapping( "/" );??
- ??
- ???????? //4、CharacterEncodingFilter ??
- ????????CharacterEncodingFilter?characterEncodingFilter?=? new ?CharacterEncodingFilter();??
- ????????characterEncodingFilter.setEncoding( "utf-8" );??
- ????????FilterRegistration?filterRegistration?=??
- ????????????????sc.addFilter( "characterEncodingFilter" ,?characterEncodingFilter);??
- ????????filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),? false ,? "/" );??
- ??
- ????}??
- }??
對于WebInitializer,請參考《 Spring4新特性——Groovy Bean定義DSL 》
?
1.1 以前的測試方式
- package ?com.sishuok.mvc.controller;??
- //省略import ??
- public ? class ?UserControllerTest?{??
- ??
- ???? private ?UserController?userController;??
- ??
- ???? @Before ??
- ???? public ? void ?setUp()?{??
- ????????userController?=? new ?UserController();??
- ???????? //安裝userCtroller依賴?比如userService ??
- ????}??
- ??
- ???? @Test ??
- ???? public ? void ?testView()?{??
- ????????MockHttpServletRequest?req?=? new ?MockHttpServletRequest();??
- ????????ModelAndView?mv?=?userController.view(1L,?req);??
- ??
- ????????ModelAndViewAssert.assertViewName(mv,? "user/view" );??
- ????????ModelAndViewAssert.assertModelAttributeAvailable(mv,? "user" );??
- ??
- ????}??
- }??
準備控制器 :我們通過new方式創建一個,然后手工查找依賴注入進去(比如從spring容器獲取/new的);
Mock Request :此處使用Spring提供的Mock API模擬一個HttpServletRequest,其他的Servlet API也提供了相應的Mock類,具體請查看Javadoc;
訪問控制器方法 :通過直接調用控制器方法進行訪問,此處無法驗證Spring MVC框架的類型轉換、數據驗證等是否正常;
ModelAndViewAssert :通過這個Assert API驗證我們的返回值是否正常;
?
對于單元測試步驟請參考: 加速Java應用開發速度3——單元/集成測試+CI ?
?
這種方式的缺點已經說過了,如不能走Spring MVC完整流程(不能走Servlet的過濾器鏈、SpringMVC的類型轉換、數據驗證、數據綁定、攔截器等等),如果做基本的測試沒問題,這種方式就是純粹的單元測試,我們想要的功能其實是一種集成測試,不過后續部分不區分。
?
1.2 安裝測試環境
spring mvc測試框架提供了兩種方式,獨立安裝和集成Web環境測試(此種方式并不會集成真正的web環境,而是通過相應的Mock API進行模擬測試,無須啟動服務器)。
?
獨立測試方式
- public ? class ?UserControllerStandaloneSetupTest?{??
- ???? private ?MockMvc?mockMvc;??
- ???? @Before ??
- ???? public ? void ?setUp()?{??
- ????????UserController?userController?=? new ?UserController();??
- ????????mockMvc?=?MockMvcBuilders.standaloneSetup(userController).build();??
- ????}??
- }??
1、首先自己創建相應的控制器,注入相應的依賴
2、通過MockMvcBuilders.standaloneSetup模擬一個Mvc測試環境,通過build得到一個MockMvc
3、MockMvc:是我們以后測試時經常使用的API,后邊介紹
?
集成Web環境方式
- //XML風格 ??
- @RunWith (SpringJUnit4ClassRunner. class )??
- @WebAppConfiguration (value?=? "src/main/webapp" )??
- @ContextHierarchy ({??
- ???????? @ContextConfiguration (name?=? "parent" ,?locations?=? "classpath:spring-config.xml" ),??
- ???????? @ContextConfiguration (name?=? "child" ,?locations?=? "classpath:spring-mvc.xml" )??
- })??
- ??
- //注解風格 ??
- //@RunWith(SpringJUnit4ClassRunner.class) ??
- //@WebAppConfiguration(value?=?"src/main/webapp") ??
- //@ContextHierarchy({ ??
- //????????@ContextConfiguration(name?=?"parent",?classes?=?AppConfig.class), ??
- //????????@ContextConfiguration(name?=?"child",?classes?=?MvcConfig.class) ??
- //}) ??
- public ? class ?UserControllerWebAppContextSetupTest?{??
- ??
- ???? @Autowired ??
- ???? private ?WebApplicationContext?wac;??
- ???? private ?MockMvc?mockMvc;??
- ??
- ???? @Before ??
- ???? public ? void ?setUp()?{??
- ????????mockMvc?=?MockMvcBuilders.webAppContextSetup(wac).build();??
- ????}??
- }??
1、@WebAppConfiguration:測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的;value指定web應用的根;
2、@ContextHierarchy:指定容器層次,即spring-config.xml是父容器,而spring-mvc.xml是子容器,請參考《 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC 》
3、通過@Autowired?WebApplicationContext wac:注入web環境的ApplicationContext容器;
4、然后通過MockMvcBuilders.webAppContextSetup(wac).build()創建一個MockMvc進行測試;
?
到此測試環境就搭建完成了,根據需要選擇使用哪種方式即可。相關配置請前往 github查看 。
?
1.3、HelloWorld
- @Test ??
- public ? void ?testView()? throws ?Exception?{??
- ????MvcResult?result?=?mockMvc.perform(MockMvcRequestBuilders.get( "/user/1" ))??
- ????????????.andExpect(MockMvcResultMatchers.view().name( "user/view" ))??
- ????????????.andExpect(MockMvcResultMatchers.model().attributeExists( "user" ))??
- ????????????.andDo(MockMvcResultHandlers.print())??
- ????????????.andReturn();??
- ??????
- ????Assert.assertNotNull(result.getModelAndView().getModel().get( "user" ));??
- }??
1、mockMvc.perform執行一個請求;
2、MockMvcRequestBuilders.get("/user/1")構造一個請求
3、ResultActions.andExpect添加執行完成后的斷言
4、ResultActions.andDo添加一個結果處理器,表示要對結果做點什么事情,比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息。
5、ResultActions.andReturn表示執行完成后返回相應的結果。
?
整個測試過程非常有規律:
1、準備測試環境
2、通過MockMvc執行請求
3.1、添加驗證斷言
3.2、添加結果處理器
3.3、得到MvcResult進行自定義斷言/進行下一步的異步請求
4、卸載測試環境
?
對于單元測試步驟請參考: 加速Java應用開發速度3——單元/集成測試+CI 。
?
1.4、了解測試API
Spring mvc測試框架提供了測試MVC需要的API,主要包括Servlet/JSP Mock、MockMvcBuilder、MockMvc、RequestBuilder、ResultMatcher、ResultHandler、MvcResult等。另外提供了幾個靜態工廠方法便于測試:MockMvcBuilders、MockMvcRequestBuilders、MockMvcResultMatchers、MockMvcResultHandlers。在使用時請使用靜態方法導入方便測試,如:
- import ? static ?org.springframework.test.web.servlet.setup.MockMvcBuilders.*;??
- import ? static ?org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;??
- import ? static ?org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;??
- import ? static ?org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;??
?
Servlet/JSP API Mock?
提供了對Servlet 3 相應API的Mock,如:
MockServletContext
MockHttpServletRequest
MockHttpServletResponse
……
具體請查看spring-test模塊的org.springframework.mock.web包。
?
?
MockMvcBuilder/MockMvcBuilders
MockMvcBuilder是用來構造MockMvc的構造器,其主要有兩個實現:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分別對應之前的兩種測試方式。對于我們來說直接使用靜態工廠MockMvcBuilders創建即可:
MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,將會從該上下文獲取相應的控制器并得到相應的MockMvc;
MockMvcBuilders.standaloneSetup(Object... controllers):通過參數指定一組控制器,這樣就不需要從上下文獲取了;
?
其中DefaultMockMvcBuilder還提供了如下API:
addFilters(Filter... filters)/addFilter(Filter filter, String... urlPatterns):添加javax.servlet.Filter過濾器
defaultRequest(RequestBuilder requestBuilder):默認的RequestBuilder,每次執行時會合并到自定義的RequestBuilder中,即提供公共請求數據的;
alwaysExpect(ResultMatcher resultMatcher):定義全局的結果驗證器,即每次執行請求時都進行驗證的規則;
alwaysDo(ResultHandler resultHandler):定義全局結果處理器,即每次請求時都進行結果處理;
dispatchOptions:DispatcherServlet是否分發OPTIONS請求方法到控制器;
?
StandaloneMockMvcBuilder繼承了DefaultMockMvcBuilder,又提供了如下API:
setMessageConverters(HttpMessageConverter<?>...messageConverters):設置HTTP消息轉換器;
setValidator(Validator validator):設置驗證器;
setConversionService(FormattingConversionService conversionService):設置轉換服務;
addInterceptors(HandlerInterceptor... interceptors)/addMappedInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors):添加spring mvc攔截器;
setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager):設置內容協商管理器;
setAsyncRequestTimeout(long timeout):設置異步超時時間;
setCustomArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers):設置自定義控制器方法參數解析器;
setCustomReturnValueHandlers(HandlerMethodReturnValueHandler... handlers):設置自定義控制器方法返回值處理器;
setHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers)/setHandlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers):設置異常解析器;
setViewResolvers(ViewResolver...resolvers):設置視圖解析器;
setSingleView(View view):設置單個視圖,即視圖解析時總是解析到這一個(僅適用于只有一個視圖的情況);
setLocaleResolver(LocaleResolver localeResolver):設置Local解析器;
setFlashMapManager(FlashMapManager flashMapManager):設置FlashMapManager,如存儲重定向數據;
setUseSuffixPatternMatch(boolean useSuffixPatternMatch):設置是否是后綴模式匹配,如“/user”是否匹配"/user.*",默認真即匹配;
setUseTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch):設置是否自動后綴路徑模式匹配,如“/user”是否匹配“/user/”,默認真即匹配;
addPlaceHolderValue(String name, String value) :添加request mapping中的占位符替代;
?
因為StandaloneMockMvcBuilder不會加載Spring MVC配置文件,因此就不會注冊我們需要的一些組件,因此就提供了如上API用于注冊我們需要的相應組件。
?
MockMvc
使用之前的MockMvcBuilder.build()得到構建好的MockMvc;這個是mvc測試的核心API,對于該API的使用方式如下:
- MvcResult?result?=?mockMvc.perform(MockMvcRequestBuilders.get( "/user/1" ))??
- ???????.andExpect(MockMvcResultMatchers.view().name( "user/view" ))??
- ???????.andExpect(MockMvcResultMatchers.model().attributeExists( "user" ))??
- ???????.andDo(MockMvcResultHandlers.print())??
- ???????.andReturn();??
perform:執行一個RequestBuilder請求,會自動執行SpringMVC的流程并映射到相應的控制器執行處理;
andExpect:添加ResultMatcher驗證規則,驗證控制器執行完成后結果是否正確;
andDo:添加ResultHandler結果處理器,比如調試時打印結果到控制臺;
andReturn:最后返回相應的MvcResult;然后進行自定義驗證/進行下一步的異步處理;
?
另外還提供了以下API:
setDefaultRequest:設置默認的RequestBuilder,用于在每次perform執行相應的RequestBuilder時自動把該默認的RequestBuilder合并到perform的RequestBuilder中;
setGlobalResultMatchers:設置全局的預期結果驗證規則,如我們通過MockMvc測試多個控制器時,假設它們都想驗證某個規則時,就可以使用這個;
setGlobalResultHandlers:設置全局的ResultHandler結果處理器;
??
RequestBuilder/MockMvcRequestBuilders
從名字可以看出,RequestBuilder用來構建請求的,其提供了一個方法buildRequest(ServletContext servletContext)用于構建MockHttpServletRequest;其主要有兩個子類MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如 文件上傳 使用),即用來Mock客戶端請求需要的所有數據。
?
MockMvcRequestBuilders主要API:
MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根據uri模板和uri變量值得到一個GET請求方式的MockHttpServletRequestBuilder;如get("/user/{id}", 1L);
MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables):同get類似,但是是POST方法;
MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables):同get類似,但是是PUT方法;
MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) :同get類似,但是是DELETE方法;
MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables):同get類似,但是是OPTIONS方法;
MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables):提供自己的Http請求方法及uri模板和uri變量,如上API都是委托給這個API;
MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables):提供 文件上傳 方式的請求,得到MockMultipartHttpServletRequestBuilder;
RequestBuilder asyncDispatch(final MvcResult mvcResult):創建一個從啟動異步處理的請求的MvcResult進行異步分派的RequestBuilder;
?
接下來再看看MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder API:
MockHttpServletRequestBuilder API:
MockHttpServletRequestBuilder header(String name, Object... values)/MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders):添加頭信息;
MockHttpServletRequestBuilder contentType(MediaType mediaType):指定請求的contentType頭信息;
MockHttpServletRequestBuilder accept(MediaType... mediaTypes)/MockHttpServletRequestBuilder accept(String... mediaTypes):指定請求的Accept頭信息;
MockHttpServletRequestBuilder content(byte[] content)/MockHttpServletRequestBuilder content(String content):指定請求Body體內容;
MockHttpServletRequestBuilder cookie(Cookie... cookies):指定請求的Cookie;
MockHttpServletRequestBuilder locale(Locale locale):指定請求的Locale;
MockHttpServletRequestBuilder characterEncoding(String encoding):指定請求字符編碼;
MockHttpServletRequestBuilder requestAttr(String name, Object value) :設置請求屬性數據;
MockHttpServletRequestBuilder sessionAttr(String name, Object value)/MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes):設置請求session屬性數據;
MockHttpServletRequestBuilder flashAttr(String name, Object value)/MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes):指定請求的flash信息,比如重定向后的屬性信息;
MockHttpServletRequestBuilder session(MockHttpSession session) :指定請求的Session;
MockHttpServletRequestBuilder principal(Principal principal) :指定請求的Principal;
MockHttpServletRequestBuilder contextPath(String contextPath) :指定請求的上下文路徑,必須以“/”開頭,且不能以“/”結尾;
MockHttpServletRequestBuilder pathInfo(String pathInfo) :請求的路徑信息,必須以“/”開頭;
MockHttpServletRequestBuilder secure(boolean secure):請求是否使用安全通道;
MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor):請求的后處理器,用于自定義一些請求處理的擴展點;
?
MockMultipartHttpServletRequestBuilder繼承自MockHttpServletRequestBuilder,又提供了如下API:
MockMultipartHttpServletRequestBuilder file(String name, byte[] content)/MockMultipartHttpServletRequestBuilder file(MockMultipartFile file):指定要上傳的文件;
?
ResultActions
調用MockMvc.perform(RequestBuilder requestBuilder)后將得到ResultActions,通過ResultActions完成如下三件事:
ResultActions andExpect(ResultMatcher matcher) :添加驗證斷言來判斷執行請求后的結果是否是預期的;
ResultActions andDo(ResultHandler handler) :添加結果處理器,用于對驗證成功后執行的動作,如輸出下請求/結果信息用于調試;
MvcResult andReturn() :返回驗證成功后的MvcResult;用于自定義驗證/下一步的異步處理;
?
ResultMatcher/MockMvcResultMatchers
ResultMatcher用來匹配執行完請求后的結果驗證,其就一個match(MvcResult result)斷言方法,如果匹配失敗將拋出相應的異常;spring mvc測試框架提供了很多***ResultMatchers來滿足測試需求。注意這些***ResultMatchers并不是ResultMatcher的子類,而是返回ResultMatcher實例的。Spring mvc測試框架為了測試方便提供了MockMvcResultMatchers靜態工廠方法方便操作;具體的API如下:
HandlerResultMatchers handler():請求的Handler驗證器,比如驗證處理器類型/方法名;此處的Handler其實就是處理請求的控制器;
RequestResultMatchers request():得到RequestResultMatchers驗證器;
ModelResultMatchers model():得到模型驗證器;
ViewResultMatchers view():得到視圖驗證器;
FlashAttributeResultMatchers flash():得到Flash屬性驗證;
StatusResultMatchers status():得到響應狀態驗證器;
HeaderResultMatchers header():得到響應Header驗證器;
CookieResultMatchers cookie():得到響應Cookie驗證器;
ContentResultMatchers content():得到響應內容驗證器;
JsonPathResultMatchers jsonPath(String expression, Object ... args)/ResultMatcher jsonPath(String expression, Matcher<T> matcher):得到Json表達式驗證器;
XpathResultMatchers xpath(String expression, Object... args)/XpathResultMatchers xpath(String expression, Map<String, String> namespaces, Object... args):得到Xpath表達式驗證器;
ResultMatcher forwardedUrl(final String expectedUrl):驗證處理完請求后轉發的url(絕對匹配);
ResultMatcher forwardedUrlPattern(final String urlPattern):驗證處理完請求后轉發的url(Ant風格模式匹配,@since spring4);
ResultMatcher redirectedUrl(final String expectedUrl):驗證處理完請求后重定向的url(絕對匹配);
ResultMatcher redirectedUrlPattern(final String expectedUrl):驗證處理完請求后重定向的url(Ant風格模式匹配,@since spring4);
?
得到相應的***ResultMatchers后,接著再調用其相應的API得到ResultMatcher,如ModelResultMatchers.attributeExists(final String... names)判斷Model屬性是否存在。具體請查看相應的API。再次就不一一列舉了。
?
?
ResultHandler/MockMvcResultHandlers
ResultHandler用于對處理的結果進行相應處理的,比如輸出整個請求/響應等信息方便調試,Spring mvc測試框架提供了MockMvcResultHandlers靜態工廠方法,該工廠提供了ResultHandler print()返回一個輸出MvcResult詳細信息到控制臺的ResultHandler實現。
?
?
MvcResult
即執行完控制器后得到的整個結果,并不僅僅是返回值,其包含了測試時需要的所有信息,如:
MockHttpServletRequest getRequest():得到執行的請求;
MockHttpServletResponse getResponse():得到執行后的響應;
Object getHandler():得到執行的處理器,一般就是控制器;
HandlerInterceptor[] getInterceptors():得到對處理器進行攔截的攔截器;
ModelAndView getModelAndView():得到執行后的ModelAndView;
Exception getResolvedException():得到HandlerExceptionResolver解析后的異常;
FlashMap getFlashMap():得到FlashMap;
Object getAsyncResult()/Object getAsyncResult(long timeout):得到異步執行的結果;
?
1.5 測試示例
測試普通控制器?
- //測試普通控制器 ??
- mockMvc.perform(get( "/user/{id}" ,? 1 ))? //執行請求 ??
- ????????.andExpect(model().attributeExists( "user" ))? //驗證存儲模型數據 ??
- ????????.andExpect(view().name( "user/view" ))? //驗證viewName ??
- ????????.andExpect(forwardedUrl( "/WEB-INF/jsp/user/view.jsp" )) //驗證視圖渲染時forward到的jsp ??
- ????????.andExpect(status().isOk()) //驗證狀態碼 ??
- ????????.andDo(print());? //輸出MvcResult到控制臺 ??
?
測試普通控制器,但是URL錯誤,即404
- //找不到控制器,404測試 ??
- MvcResult?result?=?mockMvc.perform(get( "/user2/{id}" ,? 1 ))? //執行請求 ??
- ????????.andDo(print())??
- ????????.andExpect(status().isNotFound())? //驗證控制器不存在 ??
- ????????.andReturn();??
- Assert.assertNull(result.getModelAndView());? //自定義斷言 ??
?
得到MvcResult自定義驗證 ???
- MvcResult?result?=?mockMvc.perform(get( "/user/{id}" ,? 1 )) //執行請求 ??
- ????????.andReturn();? //返回MvcResult ??
- Assert.assertNotNull(result.getModelAndView().getModel().get( "user" ));? //自定義斷言 ??
?
驗證請求參數綁定到模型數據及Flash屬性?
- mockMvc.perform(post( "/user" ).param( "name" ,? "zhang" ))? //執行傳遞參數的POST請求(也可以post("/user?name=zhang")) ??
- ????????.andExpect(handler().handlerType(UserController. class ))? //驗證執行的控制器類型 ??
- ????????.andExpect(handler().methodName( "create" ))? //驗證執行的控制器方法名 ??
- ????????.andExpect(model().hasNoErrors())? //驗證頁面沒有錯誤 ??
- ????????.andExpect(flash().attributeExists( "success" ))? //驗證存在flash屬性 ??
- ????????.andExpect(view().name( "redirect:/user" ));? //驗證視圖 ??
?
驗證請求參數驗證失敗出錯??
- mockMvc.perform(post( "/user" ).param( "name" ,? "admin" ))? //執行請求 ??
- ????????.andExpect(model().hasErrors())? //驗證模型有錯誤 ??
- ????????.andExpect(model().attributeDoesNotExist( "name" ))? //驗證存在錯誤的屬性 ??
- ????????.andExpect(view().name( "showCreateForm" ));? //驗證視圖 ??
?
文件上傳 ?
?
JSON請求/響應驗證
測試時需要安裝jackson Json和JsonPath依賴:?
- <dependency>??
- ????<groupId>com.fasterxml.jackson.core</groupId>??
- ????<artifactId>jackson-databind</artifactId>??
- ????<version>${jackson2.version}</version>??
- </dependency>??
- ??
- <dependency>??
- ????<groupId>com.jayway.jsonpath</groupId>??
- ????<artifactId>json-path</artifactId>??
- ????<version>${jsonpath.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
- String?requestBody?=? "{\"id\":1,?\"name\":\"zhang\"}" ;??
- mockMvc.perform(post( "/user" )??
- ????????????.contentType(MediaType.APPLICATION_JSON).content(requestBody)??
- ????????????.accept(MediaType.APPLICATION_JSON))? //執行請求 ??
- ????????.andExpect(content().contentType(MediaType.APPLICATION_JSON))? //驗證響應contentType ??
- ????????.andExpect(jsonPath( "$.id" ).value( 1 ));? //使用Json?path驗證JSON?請參考http://goessner.net/articles/JsonPath/ ??
- ??
- String?errorBody?=? "{id:1,?name:zhang}" ;??
- MvcResult?result?=?mockMvc.perform(post( "/user" )??
- ????????.contentType(MediaType.APPLICATION_JSON).content(errorBody)??
- ????????.accept(MediaType.APPLICATION_JSON))? //執行請求 ??
- ????????.andExpect(status().isBadRequest())? //400錯誤請求 ??
- ????????.andReturn();??
- ??
- Assert.assertTrue(HttpMessageNotReadableException. class .isAssignableFrom(result.getResolvedException().getClass())); //錯誤的請求內容體 ??
?
XML請求/響應驗證
測試時需要安裝spring oxm和xstream依賴:?
- <dependency>??
- ????<groupId>com.thoughtworks.xstream</groupId>??
- ????<artifactId>xstream</artifactId>??
- ????<version>${xsream.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
- ??
- <dependency>??
- ????<groupId>org.springframework</groupId>??
- ????<artifactId>spring-oxm</artifactId>??
- ????<version>${spring.version}</version>??
- ????<scope>test</scope>??
- </dependency>??
- //XML請求/響應 ??
- String?requestBody?=? "<user><id>1</id><name>zhang</name></user>" ;??
- mockMvc.perform(post( "/user" )??
- ????????.contentType(MediaType.APPLICATION_XML).content(requestBody)??
- ????????.accept(MediaType.APPLICATION_XML))? //執行請求 ??
- ????????.andDo(print())??
- ????????.andExpect(content().contentType(MediaType.APPLICATION_XML))? //驗證響應contentType ??
- ????????.andExpect(xpath( "/user/id/text()" ).string( "1" ));? //使用XPath表達式驗證XML?請參考http://www.w3school.com.cn/xpath/ ??
- ??
- String?errorBody?=? "<user><id>1</id><name>zhang</name>" ;??
- MvcResult?result?=?mockMvc.perform(post( "/user" )??
- ????????.contentType(MediaType.APPLICATION_XML).content(errorBody)??
- ????????.accept(MediaType.APPLICATION_XML))? //執行請求 ??
- ????????.andExpect(status().isBadRequest())? //400錯誤請求 ??
- ????????.andReturn();??
- ??
- Assert.assertTrue(HttpMessageNotReadableException. class .isAssignableFrom(result.getResolvedException().getClass())); //錯誤的請求內容體 ??
?
異常處理??
- //異常處理 ??
- MvcResult?result?=?mockMvc.perform(get( "/user/exception" ))? //執行請求 ??
- ????????.andExpect(status().isInternalServerError())? //驗證服務器內部錯誤 ??
- ????????.andReturn();??
- ??
- Assert.assertTrue(IllegalArgumentException. class .isAssignableFrom(result.getResolvedException().getClass()));??
?
靜態資源?
- //靜態資源 ??
- mockMvc.perform(get( "/static/app.js" ))? //執行請求 ??
- ????????.andExpect(status().isOk())? //驗證狀態碼200 ??
- ????????.andExpect(content().string(CoreMatchers.containsString( "var" ))); //驗證渲染后的視圖內容包含var ??
- ??
- mockMvc.perform(get( "/static/app1.js" ))? //執行請求 ??
- ????????.andExpect(status().isNotFound());?? //驗證狀態碼404 ??
異步測試?
- //Callable ??
- MvcResult?result?=?mockMvc.perform(get( "/user/async1?id=1&name=zhang" ))? //執行請求 ??
- ????????.andExpect(request().asyncStarted())??
- ????????.andExpect(request().asyncResult(CoreMatchers.instanceOf(User. class )))? //默認會等10秒超時 ??
- ????????.andReturn();??
- ??
- mockMvc.perform(asyncDispatch(result))??
- ????????.andExpect(status().isOk())??
- ????????.andExpect(content().contentType(MediaType.APPLICATION_JSON))??
- ????????.andExpect(jsonPath( "$.id" ).value( 1 ));??
- //DeferredResult ??
- result?=?mockMvc.perform(get( "/user/async2?id=1&name=zhang" ))? //執行請求 ??
- ????????.andExpect(request().asyncStarted())??
- ????????.andExpect(request().asyncResult(CoreMatchers.instanceOf(User. class )))?? //默認會等10秒超時 ??
- ????????.andReturn();??
- ??
- mockMvc.perform(asyncDispatch(result))??
- ????????.andExpect(status().isOk())??
- ????????.andExpect(content().contentType(MediaType.APPLICATION_JSON))??
- ????????.andExpect(jsonPath( "$.id" ).value( 1 ));??
此處請在第一次請求時加上 andExpect(request().asyncResult(CoreMatchers.instanceOf(User.class)))這樣會等待結果返回/超時,無須自己設置線程等待了;此處注意request().asyncResult一定是在第一次請求發出;然后第二次通過asyncDispatch進行異步請求。
?
添加自定義過濾器
- mockMvc?=?webAppContextSetup(wac).addFilter( new ?MyFilter(),? "/*" ).build();??
- mockMvc.perform(get( "/user/1" ))??
- ????????.andExpect(request().attribute( "filter" ,? true ));??
?
全局配置?
- mockMvc?=?webAppContextSetup(wac)??
- ????????.defaultRequest(get( "/user/1" ).requestAttr( "default" ,? true ))? //默認請求?如果其是Mergeable類型的,會自動合并的哦mockMvc.perform中的RequestBuilder ??
- ????????.alwaysDo(print())?? //默認每次執行請求后都做的動作 ??
- ????????.alwaysExpect(request().attribute( "default" ,? true ))? //默認每次執行后進行驗證的斷言 ??
- ????????.build();??
- ??
- mockMvc.perform(get( "/user/1" ))??
- ????????.andExpect(model().attributeExists( "user" ));??
?
以上代碼請參考 我的github 。更多參考示例請 前往Spring github 。?
?
只要記住測試步驟,按照步驟操作,整個測試過程是非常容易理解的:
1、準備測試環境
2、通過MockMvc執行請求
3.1、添加驗證斷言
3.2、添加結果處理器
3.3、得到MvcResult進行自定義斷言/進行下一步的異步請求
4、卸載測試環境
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
