《单元测试:(一)在已完成的 spring + mybatis 项目中加入 dbunit 完成 dao 层自动化测试》
《单元测试:(二) 使用 mockito 对 service 层自动化测试入门》
《单元测试:(三) powermock + spring mvc mock 对 controller 层自动化测试入门》
powermock 版本 1.7.0RC4,使用 powermock 的原因是因为能对静态方法进行验证。
一 在 pom.xml 中添加 powermock 需要的库
<!-- powermock 测试框架 -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
</dependency>
二 在需要使用 powermock 测试的类上方使用对应的注解 @RunWith(PowerMockRunner.class)
三 在测试类中需要模拟的对象定义上方使用 @Mock 或者 @InjectMocks 注解,其中 @Mock 是对于测试类的依赖类的注解,被 @Mock 的对象所有方法都不真实调用;@InjectMocks 是对待测试类的注解,@InjectMocks 注解过的对象所有方法都会进入方法代码执行。
四 在测试类的启动方法中使用 spring mvc mock 初始化需要被测试的 controller 类实例,用来模拟 http 请求。测试类的启动方法就是在想要作为启动方法的的类上加 @Before 注解
五 在测试方法中使用 spring mvc mock 以及 powermock 进行 mock 对象初始化、被测试对象方法的调用验证、mock依赖对象方法的调用验证等,代码片段如下:
import net.ioooi.practice.base.domain.Admin;
import net.ioooi.practice.base.model.AdminShortListItem;
import net.ioooi.practice.base.service.AdminService;
import net.ioooi.practice.base.util.ValidationUtil;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Created by bel on 2017/4/18.
*/
@Ignore
public class AdminControllerTest {
protected MockMvc mockMvc;
@Mock
private AdminService adminService;
@InjectMocks
private AdminController adminController;
@Before
public void setUp() {
//mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); // 使用mockito的时候不能用这个,否则不会调用mockito虚拟的对象
//MockitoAnnotations.initMocks(this); // 使用mockito的时候用的,使用powermock的时候去掉
mockMvc = MockMvcBuilders.standaloneSetup(adminController).build(); // 使用mockito的时候用这个,否则不会调用mockito虚拟的对象
}
@Test
public void testList() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/admin/list")) // 模拟发出 http 请求
.andExpect(MockMvcResultMatchers.view().name("admin-list")) // 请求后期待得到 admin-list 路径
.andExpect(MockMvcResultMatchers.status().isOk()); // 请求后期待返回正常
}
@Test
public void testSearch() throws Exception {
when(adminService.getAdminShortListCountBySearchKey(anyString())).thenReturn(1);
AdminShortListItem item = new AdminShortListItem();
item.setStatus(1);
item.setId(1);
item.setUserName("testsearch");
item.setPhone("18900000000");
List<AdminShortListItem> list = new ArrayList<AdminShortListItem>();
list.add(item);
when(adminService.getAdminShortListBySearchKey(anyString(), anyInt(), anyInt())).thenReturn(list);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/admin/search")
.contentType(MediaType.APPLICATION_JSON) // 设置 http 请求内容类型
.characterEncoding("UTF-8") // 设置请求编码
.accept(MediaType.APPLICATION_JSON) // 设置请求数据类型为 json
.param("draw", "") // 设置请求传递的参数
.param("start", "0")
.param("length", "10")
.param("search", "")
.param("recordsTotal", "0"))
.andExpect(MockMvcResultMatchers.status().isOk()) // 验证请求后服务端返回正常状态
.andReturn();
String expectedJsonResult = "{\"recordsFiltered\":1,\"data\":[{\"id\":1,\"userName\":\"testsearch\",\"phone\":\"18900000000\",\"status\":1}],\"draw\":\"\",\"recordsTotal\":1}";
assertTrue(expectedJsonResult.equals(result.getResponse().getContentAsString()));
verify(adminService, times(1)).getAdminShortListCountBySearchKey(anyString());
verify(adminService, times(1)).getAdminShortListBySearchKey(anyString(), anyInt(), anyInt());
}
@Test
public void testChangeStatus() throws Exception {
when(adminService.changeUserStatusByUserIDs(any(Integer[].class), anyInt())).thenReturn(true);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/admin/change_status")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.accept(MediaType.APPLICATION_JSON)
.param("ids[]", "-1") // 传入数组的时候不能再加方括号,例如[1,2],需要去掉1,2
.param("status", "0"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String expectedJsonResult = "{\"status\":true,\"message\":\"修改成功\",\"data\":null}";
assertTrue(expectedJsonResult.equals(result.getResponse().getContentAsString()));
}
@Test
@PrepareForTest({ ValidationUtil.class })
public void testAdd() throws Exception {
when(adminService.regist(any(Admin.class))).thenReturn(true);
PowerMockito.mockStatic(ValidationUtil.class);
PowerMockito.when(ValidationUtil.beanValidate(any(Admin.class))).thenReturn(""); // mock调用静态方法
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/admin/add")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.accept(MediaType.APPLICATION_JSON)
.param("userName", "testadd")
.param("password", "testadd")
.param("email", "testadd@test.com")
.param("phone", "18900000000"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String expectedJsonResult = "{\"status\":true,\"message\":\"添加成功\",\"data\":null}";
assertTrue(expectedJsonResult.equals(result.getResponse().getContentAsString()));
PowerMockito.verifyStatic(); // 本句和下一句联合起来对静态方法进行调用
ValidationUtil.beanValidate((any(Admin.class)));
}
@Test
@PrepareForTest({ ValidationUtil.class })
public void testModify() throws Exception {
Admin admin = new Admin();
when(adminService.getAdminById(anyInt())).thenReturn(admin);
when(adminService.modify(admin)).thenReturn(true);
PowerMockito.mockStatic(ValidationUtil.class);
PowerMockito.when(ValidationUtil.beanValidate(any(Admin.class))).thenReturn(""); // mock调用静态方法
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/admin/modify")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.accept(MediaType.APPLICATION_JSON)
.param("id", "1")
.param("email", "testadd@test.com")
.param("phone", "18900000000"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String expectedJsonResult = "{\"status\":true,\"message\":\"修改成功\",\"data\":null}";
assertTrue(expectedJsonResult.equals(result.getResponse().getContentAsString()));
verify(adminService).getAdminById(anyInt());
verify(adminService).modify(admin);
PowerMockito.verifyStatic(); // 本句和下一句联合起来对静态方法进行调用
ValidationUtil.beanValidate((any(Admin.class)));
}
}