本文主要總結自己近期在項目中對MVC集成測試的實踐及理解,因為先前對這塊未實踐過。主要參考了官方文檔《
11.3.6 Spring MVC Test Framework》這一章節(jié)內容,涉及到
Spring TestContext Framework、
TestNG 和
Mockito 這3個測試框架,完全基于Spring自動裝配注解(@Autowired)實現,不需要定義額外的setter或構造器來注入bean,也不需要通過Mockito的@Mock和MockitoAnnotations.initMocks(this)代碼方式實現實例化,而是通過靜態(tài)工廠方法Mockito.mock(...)在XML中實現bean實例初始化。
廢話不多說了,看一下需要幾步就能搞定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()) // 打印整個請求與響應細節(jié)
- .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哈哈~
玩得開心!^_^
本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現有害或侵權內容,請
點擊舉報。