废话不多说了,看一下需要几步就能搞定MVC Controller与Service层的集成测试。(如果你现在也正好使用Spring Test框架,可以看看下面对TestNG基类封装的代码,我觉得自己写得还可以。小小赞美一下啦~)
1. 定义底层Service接口及实现
/** * User service. */ public interface UserService { /** * Gets user info for specified user ID. * * @param id user ID * @return */ User getUserInfo(long id); /** * Updates user info. * * @param user user info * @return -1 means fail, 0 means success. */ int updateUserInfo(User user); }
/** * User query service. */ public interface UserQueryService { /** * Gets user name for specified user ID. * * @param userId user ID * @return */ String getUserName(long userId); /** * Updates user name for specified user ID. * * @param userId * @param userName * @return -1 means fail, 0 means success. */ int updateUserName(long userId, String userName); }
/** * User query service implementation. */ @Service public class UserQueryServiceImpl implements UserQueryService { @Autowired private UserService userService; @Override public String getUserName(long userId) { User user = this.userService.getUserInfo(userId); return user != null ? user.getName() : ""; } @Override public int updateUserName(long userId, String userName) { User user = new User(userId, userName); int udpateResult = this.userService.updateUserInfo(user); return udpateResult; } }
2. 为 Controller 层的每一接口定义一对 Request与Response(可重用的,就别多定义啦!~\(≧▽≦)/~)
/** * Base request info. * * @author Bert Lee * @version 2014-8-19 */ @JsonIgnoreProperties(ignoreUnknown = true) // 忽略多传的参数 public class BaseRequest { }
/** * User ID request info. * * @author Bert Lee * @version 2014-8-19 */ public class UserIDRequest extends BaseRequest { @JsonProperty("id") @NotNull(message = "id param is null") @Min(value = 1, message = "id param must be great or equal than \\{{value}\\}") // 4.3. Message interpolation -《JSR 303: Bean Validation》 protected long id; public long getId() { return id; } public void setId(long id) { this.id = id; } @Override public String toString() { return "UserIDRequest [id=" + id + "]"; } }
/** * User name response info. */ public class UserNameResponse { @JsonProperty("name") protected String name = ""; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "UserNameResponse [name=" + name + "]"; } }
/** * User info request info. */ public class UserInfoRequest extends UserIDRequest { @JsonProperty("name") @NotNull(message = "name param is null") @Size(min = 1, message = "name param is empty") protected String userName; // 变量名与请求参数名不一样,在@RequestBody中用到 public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Override public String toString() { return "UserInfoRequest [userName=" + userName + ", id=" + id + "]"; } }
/** * User modify result response info. */ public class UserResultResponse { @JsonProperty("ret") protected int result; @JsonProperty("ret_msg") protected String resultMessage; public UserResultResponse() { this.result = 0; this.resultMessage = "ok"; } public int getResult() { return result; } public void setResult(int result) { this.result = result; } public String getResultMessage() { return resultMessage; } public void setResultMessage(String resultMessage) { this.resultMessage = resultMessage; } @Override public String toString() { return "UserResultResponse [result=" + result + ", resultMessage=" + resultMessage + "]"; } }
3. 实现 Controller 层逻辑
/** * User Controller. * * @author Bert Lee * @version 2014-8-19 */ @Controller @RequestMapping(value = "/user") public class UserController { @Autowired private UserQueryService userQueryService; @RequestMapping(value = "/get_user_name", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public UserNameResponse getUserName(@Valid UserIDRequest userIDRequest) { long userId = userIDRequest.getId(); String userName = this.userQueryService.getUserName(userId); UserNameResponse response = new UserNameResponse(); if (!StringUtils.isEmpty(userName)) { response.setName(userName); } return response; } @RequestMapping(value = "/update_user_name", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public UserResultResponse updateUserName(@Valid @RequestBody UserInfoRequest userInfoRequest) { // JSON request body map UserResultResponse response = new UserResultResponse(); long userId = userInfoRequest.getId(); String userName = userInfoRequest.getUserName(); int result = this.userQueryService.updateUserName(userId, userName); if (result < 0) { response.setResult(result); response.setResultMessage("update operation is fail"); } return response; } }
4. 实现一个Service与Controller层的抽象测试基类(用于集成TestNG与MVC Test框架,且自动加载配置文件)
/** * Abstract base test class for TestNG. * * @author Bert Lee * @version 2014-8-19 */ @ContextConfiguration("classpath:META-INF/spring/test-context.xml") // 集成应用上下文并加载默认的beans XML配置 public abstract class AbstractTestNGTest extends AbstractTestNGSpringContextTests { // 集成TestNG /** * Initializes the test context. */ @BeforeSuite(alwaysRun = true) public void init() { // MockitoAnnotations.initMocks(this); // 基于Spring自动装配注解,这里不再需要初始化 } }
/** * Abstract controller tier base test class for TestNG. * * @author Bert Lee * @version 2014-8-19 */ @WebAppConfiguration("src/test/java") // 集成Web应用上下文 public abstract class AbstractControllerTestNGTest extends AbstractTestNGTest { /** * MVC mock */ protected MockMvc mockMvc; /** * Gets the tested controller. * * @return the controller that is tested */ protected abstract Object getController(); /** * Setups the tested controller in MVC Mock environment. */ @BeforeClass(alwaysRun = true) public void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(this.getController()).build(); } /** * Mocks the GET request. * * @param url * @param params * @param expectedContent * @throws Exception */ protected void getMock(String url, Object[] params, String expectedContent) throws Exception { // 2. 构造GET请求 MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders .get(url, params); this.jsonRequestMock(requestBuilder, expectedContent); } /** * Mocks the POST request. * * @param url * @param paramsJson * @param expectedContent * @throws Exception */ protected void postMock(String url, String paramsJson, String expectedContent) throws Exception { // 2. 构造POST请求 MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders .post(url) .content(paramsJson) // 设置request body请求体,服务于"@RequestBody" ; this.jsonRequestMock(requestBuilder, expectedContent); } /** * Mocks the request for "application/json;charset=UTF-8" media type (Content-Type). * * @param requestBuilder * @param expectedContent * @throws Exception */ private void jsonRequestMock(MockHttpServletRequestBuilder requestBuilder, String expectedContent) throws Exception { // 2. 设置HTTP请求属性 requestBuilder.contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .characterEncoding(CharEncoding.UTF_8) ; // 3. 定义期望响应行为 this.mockMvc.perform(requestBuilder) .andDo(print()) // 打印整个请求与响应细节 .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().string(expectedContent)) // 校验是否是期望的结果 ; } }
5. 实现Controller与Service层的测试逻辑
/** * Test for {@link UserController}. * * @author Bert Lee * @version 2014-8-19 */ public class UserControllerTest extends AbstractControllerTestNGTest { // tested controller @Autowired private UserController userControllerTest; // mocked service (被依赖的服务) @Autowired private UserQueryService userQueryService; @Test(dataProvider = "getUserName") public void getUserName(Object[] params, String userName, String expectedContent) throws Exception { // 1. 定义"被依赖的服务"的方法行为 when(this.userQueryService.getUserName(anyLong())).thenReturn(userName); this.getMock("/user/get_user_name?id={id}", params, expectedContent); } @DataProvider(name = "getUserName") protected static final Object[][] getUserNameTestData() { Object[][] testData = new Object[][] { { new Object[] { "23" }, "Bert Lee", "{\"name\":\"Bert Lee\"}" }, }; return testData; } @Test(dataProvider = "updateUserName") public void updateUserName(String paramsJson, Integer result, String expectedContent) throws Exception { // 1. 定义"被依赖的服务"的方法行为 when(this.userQueryService.updateUserName(anyLong(), anyString())).thenReturn(result); this.postMock("/user/update_user_name", paramsJson, expectedContent); } @DataProvider(name = "updateUserName") protected static final Object[][] updateUserNameTestData() { Object[][] testData = new Object[][] { { "{\"id\":23,\"name\":\"Bert Lee\"}", 0, "{\"ret\":0,\"ret_msg\":\"ok\"}" }, }; return testData; } @Override public Object getController() { return this.userControllerTest; } }
/** * Test for {@link UserQueryService}. * * @author Bert Lee * @version 2014-7-25 */ public class UserQueryServiceTest extends AbstractTestNGTest { // tested service @Autowired private UserQueryService userQueryServiceTest; // mocked service (被依赖的服务) @Autowired private UserService userService; @Test(dataProvider = "getUserName") public void getUserName(User user, String expected) { // 1. 定义"被依赖的服务"的方法行为 when(userService.getUserInfo(anyLong())).thenReturn(user); String userName = this.userQueryServiceTest.getUserName(3L); assertEquals(userName, expected); } @DataProvider(name = "getUserName") protected static final Object[][] getUserNameTestData() { Object[][] testData = new Object[][] { { null, "" }, { new User(3L, ""), "" }, { new User(10L, "Edward Lee"), "Edward Lee" }, { new User(23L, "李华刚@!~#$%^&"), "李华刚@!~#$%^&" }, }; return testData; } }
6. 定义XML bean配置文件,实现测试对象及被依赖的服务的自动注入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd "> <bean id="userControllerTest" class="com.weibo.web.UserController" /> <!-- 被依赖的服务 --> <bean id="userQueryService" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.weibo.service.UserQueryService" /> </bean> <bean id="userQueryServiceTest" class="com.weibo.service.impl.UserQueryServiceImpl" /> <!-- 被依赖的服务 --> <bean id="userService" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.weibo.service.UserService" /> </bean> </beans>
7. 运行测试用例,OK!
7步搞定,挺简单的吧,O(∩_∩)O哈哈~
玩得开心!^_^