您的位置:首页 > 编程语言 > Java开发

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

2016-11-07 13:31 477 查看
本文主要总结自己近期在项目中对MVC集成测试的实践及理解,因为先前对这块未实践过。主要参考了官方文档《11.3.6
Spring MVC Test Framework》这一章节内容,涉及到 Spring TestContext FrameworkTestNG Mockito 这3个测试框架,完全基于Spring自动装配注解(@Autowired)实现,不需要定义额外的setter或构造器来注入bean,也不需要通过Mockito的@Mock和MockitoAnnotations.initMocks(this)代码方式实现实例化,而是通过静态工厂方法Mockito.mock(...)在XML中实现bean实例初始化。

 

废话不多说了,看一下需要几步就能搞定MVC Controller与Service层的集成测试。(如果你现在也正好使用Spring Test框架,可以看看下面对TestNG基类封装的代码,我觉得自己写得还可以。小小赞美一下啦~)

 

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

 

Java代码  


/** 

 * 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);  

  

}  



 

Java代码  


/** 

 * 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);  

  

}  



 

Java代码  


/** 

 * 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(可重用的,就别多定义啦!~\(≧▽≦)/~)

Java代码  


/** 

 * Base request info. 

 * 

 * @author  Bert Lee 

 * @version 2014-8-19 

 */  

@JsonIgnoreProperties(ignoreUnknown = true) // 忽略多传的参数  

public class BaseRequest {  

  

}  



 

Java代码  


/** 

 * 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 + "]";  

    }  

  

}  



 

Java代码  


/** 

 * 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 + "]";  

    }  

  

}  



 

Java代码  


/** 

 * 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 + "]";  

    }  

  

}  



 

Java代码  


/** 

 * 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 层逻辑

Java代码  


/** 

 * 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框架,且自动加载配置文件)

Java代码  


/** 

 * 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自动装配注解,这里不再需要初始化  

    }  

  

}  



 

Java代码  


/** 

 * 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层的测试逻辑

Java代码  


/** 

 * 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;  

    }  

  

}  



 

Java代码  


/** 

 * 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代码  


<?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哈哈~

 

玩得开心!^_^

Testing.zip (244.5 KB)
下载次数: 44

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: