基于Mockito+TestNG及自动装配注解的Spring MVC集成测试

由于近期项目使用了 Spring框架 开发,肯定会使用到Spring框架的 IoC容器AOP、拦截器、事务管理数据源连接池管理数据校验ValidationWeb MVC frameworkTesting

 

本文主要总结自己在项目中对MVC集成测试的实践及理解,因为对这块不熟悉。本文参考了官方文档《11.3.6 Spring MVC Test Framework》这一章节内容,涉及到 Spring TestContext FrameworkTestNGMockito 这3个测试框架。

 

废话不多说了,看一下需要几步就能搞定MVC Controller与Service层的集成测试。

 

1. 定义底层Service接口及实现

 

Java代码   收藏代码
  1. /** 
  2.  * User service. 
  3.  */  
  4. public interface UserService {  
  5.   
  6.     /** 
  7.      * Gets user info for specified user ID. 
  8.      *  
  9.      * @param id user ID 
  10.      * @return 
  11.      */  
  12.     User getUserInfo(long id);  
  13.       
  14.     /** 
  15.      * Updates user info. 
  16.      *  
  17.      * @param user user info 
  18.      * @return -1 means fail, 0 means success. 
  19.      */  
  20.     int updateUserInfo(User user);  
  21.   
  22. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User query service. 
  3.  */  
  4. public interface UserQueryService {  
  5.   
  6.     /** 
  7.      * Gets user name for specified user ID. 
  8.      *  
  9.      * @param userId user ID 
  10.      * @return 
  11.      */  
  12.     String getUserName(long userId);  
  13.       
  14.     /** 
  15.      * Updates user name for specified user ID. 
  16.      *  
  17.      * @param userId 
  18.      * @param userName 
  19.      * @return -1 means fail, 0 means success. 
  20.      */  
  21.     int updateUserName(long userId, String userName);  
  22.   
  23. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User query service implementation. 
  3.  */  
  4. @Service  
  5. public class UserQueryServiceImpl implements UserQueryService {  
  6.   
  7.     @Autowired  
  8.     private UserService userService;  
  9.   
  10.     @Override  
  11.     public String getUserName(long userId) {  
  12.         User user = this.userService.getUserInfo(userId);  
  13.         return user != null ? user.getName() : "";  
  14.     }  
  15.   
  16.     @Override  
  17.     public int updateUserName(long userId, String userName) {  
  18.         User user = new User(userId, userName);  
  19.         int udpateResult = this.userService.updateUserInfo(user);  
  20.         return udpateResult;  
  21.     }  
  22.   
  23. }  

 

 

2. 为 Controller 层的每一接口定义一对 Request与Response(可重用的,就别多定义啦!~\(≧▽≦)/~)

Java代码   收藏代码
  1. /** 
  2.  * Base request info. 
  3.  */  
  4. @JsonIgnoreProperties(ignoreUnknown = true)  
  5. public class BaseRequest {  
  6.   
  7. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User ID request info. 
  3.  */  
  4. public class UserIDRequest extends BaseRequest {  
  5.   
  6.     @JsonProperty("id")  
  7.     @NotNull(message = "id param is null")  
  8.     @Min(value = 1, message = "id param must be great or equal than 1")  
  9.     protected long id;  
  10.   
  11.     public long getId() {  
  12.         return id;  
  13.     }  
  14.   
  15.     public void setId(long id) {  
  16.         this.id = id;  
  17.     }  
  18.   
  19.     @Override  
  20.     public String toString() {  
  21.         return "UserIDRequest [id=" + id + "]";  
  22.     }  
  23.   
  24. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User name response info. 
  3.  */  
  4. public class UserNameResponse {  
  5.   
  6.     @JsonProperty("name")  
  7.     protected String name = "";  
  8.   
  9.     public String getName() {  
  10.         return name;  
  11.     }  
  12.   
  13.     public void setName(String name) {  
  14.         this.name = name;  
  15.     }  
  16.   
  17.     @Override  
  18.     public String toString() {  
  19.         return "UserNameResponse [name=" + name + "]";  
  20.     }  
  21.   
  22. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User info request info. 
  3.  */  
  4. public class UserInfoRequest extends UserIDRequest {  
  5.   
  6.     @JsonProperty("name")  
  7.     @NotNull(message = "name param is null")  
  8.     @Size(min = 1, message = "name param is empty")  
  9.     protected String userName;  
  10.   
  11.     public String getUserName() {  
  12.         return userName;  
  13.     }  
  14.   
  15.     public void setUserName(String userName) {  
  16.         this.userName = userName;  
  17.     }  
  18.   
  19.     @Override  
  20.     public String toString() {  
  21.         return "UserInfoRequest [userName=" + userName + ", id=" + id + "]";  
  22.     }  
  23.   
  24. }  

 

Java代码   收藏代码
  1. /** 
  2.  * User modify result response info. 
  3.  */  
  4. public class UserResultResponse {  
  5.   
  6.     @JsonProperty("ret")  
  7.     protected int result;  
  8.       
  9.     @JsonProperty("ret_msg")  
  10.     protected String resultMessage;  
  11.   
  12.     public UserResultResponse() {  
  13.         this.result = 0;  
  14.         this.resultMessage = "ok";  
  15.     }  
  16.   
  17.     public int getResult() {  
  18.         return result;  
  19.     }  
  20.   
  21.     public void setResult(int result) {  
  22.         this.result = result;  
  23.     }  
  24.   
  25.     public String getResultMessage() {  
  26.         return resultMessage;  
  27.     }  
  28.   
  29.     public void setResultMessage(String resultMessage) {  
  30.         this.resultMessage = resultMessage;  
  31.     }  
  32.   
  33.     @Override  
  34.     public String toString() {  
  35.         return "UserResultResponse [result=" + result + ", resultMessage="  
  36.                 + resultMessage + "]";  
  37.     }  
  38.   
  39. }  

 

 

3. 实现 Controller 层逻辑

Java代码   收藏代码
  1. /** 
  2.  * User Controller. 
  3.  * 
  4.  * @author  Bert Lee 
  5.  * @version 2014-8-19 
  6.  */  
  7. @Controller  
  8. @RequestMapping(value = "/user")  
  9. public class UserController {  
  10.   
  11.     @Autowired  
  12.     private UserQueryService userQueryService;  
  13.       
  14.       
  15.     @RequestMapping(value = "/get_user_name", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)  
  16.     @ResponseBody  
  17.     public UserNameResponse getUserName(@Valid UserIDRequest userIDRequest) {  
  18.         long userId = userIDRequest.getId();  
  19.         String userName = this.userQueryService.getUserName(userId);  
  20.           
  21.         UserNameResponse response = new UserNameResponse();  
  22.         if (!StringUtils.isEmpty(userName)) {  
  23.             response.setName(userName);  
  24.         }  
  25.           
  26.         return response;  
  27.     }  
  28.       
  29.     @RequestMapping(value = "/update_user_name", method = RequestMethod.POST,  
  30.             consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)  
  31.     @ResponseBody  
  32.     public UserResultResponse updateUserName(@Valid @RequestBody UserInfoRequest userInfoRequest) {  
  33.         UserResultResponse response = new UserResultResponse();  
  34.           
  35.         long userId = userInfoRequest.getId();  
  36.         String userName = userInfoRequest.getUserName();  
  37.         int result = this.userQueryService.updateUserName(userId, userName);  
  38.         if (result < 0) {  
  39.             response.setResult(result);  
  40.             response.setResultMessage("update operation is fail");  
  41.         }  
  42.           
  43.         return response;  
  44.     }  
  45.   
  46. }  

 

 

4. 实现一个Service与Controller层的抽象测试基类(用于集成TestNG与MVC Test框架,且自动加载配置文件)

Java代码   收藏代码
  1. /** 
  2.  * Abstract base test class for TestNG. 
  3.  * 
  4.  * @author  Bert Lee 
  5.  * @version 2014-8-19 
  6.  */  
  7. @ContextConfiguration("classpath:META-INF/spring/test-context.xml")  
  8. public abstract class AbstractTestNGTest extends AbstractTestNGSpringContextTests {  
  9.   
  10.     /** 
  11.      * Initializes the test context. 
  12.      */  
  13.     @BeforeSuite(alwaysRun = true)  
  14.     public void init() {  
  15. //      MockitoAnnotations.initMocks(this); // 基于Spring自动装配注解,这里不再需要初始化  
  16.     }  
  17.   
  18. }  

 

Java代码   收藏代码
  1. /** 
  2.  * Abstract controller tier base test class for TestNG. 
  3.  * 
  4.  * @author  Bert Lee 
  5.  * @version 2014-8-19 
  6.  */  
  7. @WebAppConfiguration("src/test/java")  
  8. public abstract class AbstractTestNGControllerTest extends AbstractTestNGTest {  
  9.   
  10.     protected MockMvc mockMvc;  
  11.       
  12.     /** 
  13.      * Gets the tested controller. 
  14.      *  
  15.      * @return the controller that is tested 
  16.      */  
  17.     protected abstract Object getController();  
  18.       
  19.     /** 
  20.      * Setups the tested controller in MVC Mock environment. 
  21.      */  
  22.     @BeforeClass(alwaysRun = true)  
  23.     public void setup() {  
  24.         this.mockMvc = MockMvcBuilders.standaloneSetup(this.getController()).build();  
  25.     }  
  26.       
  27.     /** 
  28.      * Mocks the GET request. 
  29.      *  
  30.      * @param url 
  31.      * @param params 
  32.      * @param expectedContent 
  33.      * @throws Exception 
  34.      */  
  35.     protected void getMock(String url, Object[] params, String expectedContent) throws Exception {  
  36.         // 2. 构造GET请求  
  37.         MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders  
  38.                 .get(url, params);  
  39.           
  40.         this.jsonRequestMock(requestBuilder, expectedContent);  
  41.     }  
  42.       
  43.     /** 
  44.      * Mocks the POST request. 
  45.      *  
  46.      * @param url 
  47.      * @param paramsJson 
  48.      * @param expectedContent 
  49.      * @throws Exception 
  50.      */  
  51.     protected void postMock(String url, String paramsJson, String expectedContent) throws Exception {  
  52.         // 2. 构造POST请求  
  53.         MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders  
  54.                 .post(url)  
  55.                 .content(paramsJson) // 设置请求体,服务于"@RequestBody"  
  56.                 ;  
  57.           
  58.         this.jsonRequestMock(requestBuilder, expectedContent);  
  59.     }  
  60.       
  61.     /** 
  62.      * Mocks the request for "application/json;charset=UTF-8" Content-Type. 
  63.      *  
  64.      * @param requestBuilder 
  65.      * @param expectedContent 
  66.      * @throws Exception 
  67.      */  
  68.     private void jsonRequestMock(MockHttpServletRequestBuilder requestBuilder, String expectedContent) throws Exception {  
  69.         // 2. 设置HTTP请求属性  
  70.         requestBuilder.contentType(MediaType.APPLICATION_JSON)  
  71.                 .accept(MediaType.APPLICATION_JSON)  
  72.                 .characterEncoding(CharEncoding.UTF_8)  
  73.                 ;  
  74.           
  75.         // 3. 定义期望响应行为  
  76.         this.mockMvc.perform(requestBuilder)  
  77.                 .andDo(print()) // 打印整个请求与响应细节  
  78.                 .andExpect(status().isOk())  
  79.                 .andExpect(content().contentType(MediaType.APPLICATION_JSON))  
  80.                 .andExpect(content().string(expectedContent)) // 校验是否是期望的结果  
  81.                 ;  
  82.     }  
  83.   
  84. }  

 

5. 实现Controller与Service层的测试逻辑

Java代码   收藏代码
  1. /** 
  2.  * Test for {@link UserController}. 
  3.  * 
  4.  * @author  Bert Lee 
  5.  * @version 2014-8-19 
  6.  */  
  7. public class UserControllerTest extends AbstractTestNGControllerTest {  
  8.   
  9.     // tested controller  
  10.     @Autowired  
  11.     private UserController userControllerTest;  
  12.       
  13.     // mocked service (被依赖的服务)  
  14.     @Autowired  
  15.     private UserQueryService userQueryService;  
  16.       
  17.       
  18.     @Test(dataProvider = "getUserName")  
  19.     public void getUserName(Object[] params, String userName, String expectedContent) throws Exception {  
  20.         // 1. 定义"被依赖的服务"的方法行为  
  21.         when(this.userQueryService.getUserName(anyLong())).thenReturn(userName);  
  22.           
  23.         this.getMock("/user/get_user_name?id={id}", params, expectedContent);  
  24.     }  
  25.     @DataProvider(name = "getUserName")  
  26.     protected static final Object[][] getUserNameTestData() {  
  27.         Object[][] testData = new Object[][] {  
  28.                 { new Object[] { "23" }, "Bert Lee""{\"name\":\"Bert Lee\"}" },  
  29.         };  
  30.         return testData;  
  31.     }  
  32.       
  33.     @Test(dataProvider = "updateUserName")  
  34.     public void updateUserName(String paramsJson, Integer result, String expectedContent) throws Exception {  
  35.         // 1. 定义"被依赖的服务"的方法行为  
  36.         when(this.userQueryService.updateUserName(anyLong(), anyString())).thenReturn(result);  
  37.           
  38.         this.postMock("/user/update_user_name", paramsJson, expectedContent);  
  39.     }  
  40.     @DataProvider(name = "updateUserName")  
  41.     protected static final Object[][] updateUserNameTestData() {  
  42.         Object[][] testData = new Object[][] {  
  43.                 { "{\"id\":23,\"name\":\"Bert Lee\"}"0"{\"ret\":0,\"ret_msg\":\"ok\"}" },  
  44.         };  
  45.         return testData;  
  46.     }  
  47.       
  48.     @Override  
  49.     public Object getController() {  
  50.         return this.userControllerTest;  
  51.     }  
  52.   
  53. }  

 

Java代码   收藏代码
  1. /** 
  2.  * Test for {@link UserQueryService}. 
  3.  * 
  4.  * @author  Bert Lee 
  5.  * @version 2014-7-25 
  6.  */  
  7. public class UserQueryServiceTest extends AbstractTestNGTest {  
  8.   
  9.     // tested service  
  10.     @Autowired  
  11.     private UserQueryService userQueryServiceTest;  
  12.       
  13.     // mocked service (被依赖的服务)  
  14.     @Autowired  
  15.     private UserService userService;  
  16.       
  17.       
  18.     @Test(dataProvider = "getUserName")  
  19.     public void getUserName(User user, String expected) {  
  20.         // 1. 定义"被依赖的服务"的方法行为  
  21.         when(userService.getUserInfo(anyLong())).thenReturn(user);  
  22.           
  23.         String userName = this.userQueryServiceTest.getUserName(3L);  
  24.         assertEquals(userName, expected);  
  25.     }  
  26.     @DataProvider(name = "getUserName")  
  27.     protected static final Object[][] getUserNameTestData() {  
  28.         Object[][] testData = new Object[][] {  
  29.                 { null"" },  
  30.                 { new User(3L, ""), "" },  
  31.                 { new User(10L, "Edward Lee"), "Edward Lee" },  
  32.                 { new User(23L, "李华刚@!~#$%^&"), "李华刚@!~#$%^&" },  
  33.         };  
  34.         return testData;  
  35.     }  
  36.   
  37. }  

 

6. 定义XML bean配置文件,实现测试对象及被依赖的服务的自动注入

Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.       xsi:schemaLocation="  
  5.       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd ">  
  6.   
  7.     <bean id="userControllerTest" class="com.weibo.web.UserController" />  
  8.     <!-- 被依赖的服务 -->  
  9.     <bean id="userQueryService" class="org.mockito.Mockito" factory-method="mock">  
  10.       <constructor-arg value="com.weibo.service.UserQueryService" />  
  11.     </bean>  
  12.       
  13.     <bean id="userQueryServiceTest" class="com.weibo.service.impl.UserQueryServiceImpl" />  
  14.     <!-- 被依赖的服务 -->  
  15.     <bean id="userService" class="org.mockito.Mockito" factory-method="mock">  
  16.       <constructor-arg value="com.weibo.service.UserService" />  
  17.     </bean>  
  18.       
  19. </beans>  

 

7. 运行测试用例,OK!

 

7步搞定,挺简单的吧,O(∩_∩)O哈哈~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值