6. verify语句
验证是确认在模拟过程中,被测试方法是否已按预期方式与其任何依赖方法进行了交互。
格式:
Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs);
用途:
用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。
案例:
6.1. 验证调用方法
public class ListTest { @Test public void testGet() { List<Integer> mockList = PowerMockito.mock(List.class); PowerMockito.doNothing().when(mockList).clear(); mockList.clear(); Mockito.verify(mockList).clear(); } } |
6.2. 验证调用次数
public class ListTest { @Test public void testGet() { List<Integer> mockList = PowerMockito.mock(List.class); PowerMockito.doNothing().when(mockList).clear(); mockList.clear(); Mockito.verify(mockList, Mockito.times(1)).clear(); } } |
除times外,Mockito还支持atLeastOnce、atLeast、only、atMostOnce、atMost等次数验证器。
6.3. 验证调用顺序
public class ListTest { @Test public void testAdd() { List<Integer> mockedList = PowerMockito.mock(List.class); PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt()); mockedList.add(1); mockedList.add(2); mockedList.add(3); InOrder inOrder = Mockito.inOrder(mockedList); inOrder.verify(mockedList).add(1); inOrder.verify(mockedList).add(2); inOrder.verify(mockedList).add(3); } } |
6.4. 验证调用参数
public class ListTest { @Test public void testArgumentCaptor() { Integer[] expecteds = new Integer[] {1, 2, 3}; List<Integer> mockedList = PowerMockito.mock(List.class); PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt()); for (Integer expected : expecteds) { mockedList.add(expected); } ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class); Mockito.verify(mockedList, Mockito.times(3)).add(argumentCaptor.capture()); Integer[] actuals = argumentCaptor.getAllValues().toArray(new Integer[0]); Assert.assertArrayEquals("返回值不相等", expecteds, actuals); } } |
6.5. 确保验证完毕
Mockito提供Mockito.verifyNoMoreInteractions方法,在所有验证方法之后可以使用此方法,以确保所有调用都得到验证。如果模拟对象上存在任何未验证的调用,将会抛出NoInteractionsWanted异常。
public class ListTest { @Test public void testVerifyNoMoreInteractions() { List<Integer> mockedList = PowerMockito.mock(List.class); Mockito.verifyNoMoreInteractions(mockedList); // 执行正常 mockedList.isEmpty(); Mockito.verifyNoMoreInteractions(mockedList); // 抛出异常 } } |
备注:Mockito.verifyZeroInteractions方法与Mockito.verifyNoMoreInteractions方法相同,但是目前已经被废弃。
6.6. 验证静态方法
Mockito没有静态方法的验证方法,但是PowerMock提供这方面的支持。
请 @RunWith(PowerMockRunner.class) @PrepareForTest({StringUtils.class}) public class StringUtilsTest { @Test public void testVerifyStatic() { PowerMockito.mockStatic(StringUtils.class); String expected = "abc"; StringUtils.isEmpty(expected); PowerMockito.verifyStatic(StringUtils.class); ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class); StringUtils.isEmpty(argumentCaptor.capture()); Assert.assertEquals("参数不相等", argumentCaptor.getValue(), expected); } } |
7. 私有属性
7.1. ReflectionTestUtils.setField方法
请 @Service public class UserService { @Value("${system.userLimit}") private Long userLimit; public Long getUserLimit() { return userLimit; } } public class UserServiceTest { @Autowired private UserService userService; @Test public void testGetUserLimit() { Long expected = 1000L; ReflectionTestUtils.setField(userService, "userLimit", expected); Long actual = userService.getUserLimit(); Assert.assertEquals("返回值不相等", expected, actual); } } |
注意:在测试类中,UserService实例是通过@Autowired注解加载的,如果该实例已经被动态代理,ReflectionTestUtils.setField方法设置的是代理实例,从而导致设置不生效。
7.2. Whitebox.setInternalState方法
现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。
@Service public class UserService { @Value("${system.userLimit}") private Long userLimit; public Long getUserLimit() { return userLimit; } } @RunWith(PowerMockRunner.class) public class UserServiceTest { @InjectMocks private UserService userService; @Test public void testGetUserLimit() { Long expected = 1000L; Whitebox.setInternalState(userService, "userLimit", expected); Long actual = userService.getUserLimit(); Assert.assertEquals("返回值不相等", expected, actual); } } |
注意:需要加上注解@RunWith(PowerMockRunner.class)。
8. 私有方法
8.1. 模拟私有方法
8.1.1. 通过when实现
public class UserService { private Long superUserId; public boolean isNotSuperUser(Long userId) { return !isSuperUser(userId); } private boolean isSuperUser(Long userId) { return Objects.equals(userId, superUserId); } } @RunWith(PowerMockRunner.class) @PrepareForTest({UserService.class}) public class UserServiceTest { @Test public void testIsNotSuperUser() throws Exception { Long userId = 1L; boolean expected = false; UserService userService = PowerMockito.spy(new UserService()); PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected); boolean actual = userService.isNotSuperUser(userId); Assert.assertEquals("返回值不相等", expected, actual); } } |
8.1.2. 通过stub实现
通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法的返回值,而不能模拟指定参数的返回值。
@RunWith(PowerMockRunner.class) @PrepareForTest({UserService.class}) public class UserServiceTest { @Test public void testIsNotSuperUser() throws Exception { Long userId = 1L; boolean expected = false; UserService userService = PowerMockito.spy(new UserService()); PowerMockito.stub(PowerMockito.method(UserService.class, "isSuperUser", Long.class)).toReturn(!expected); boolean actual = userService.isNotSuperUser(userId); Assert.assertEquals("返回值不相等", expected, actual; } } |
8.3. 测试私有方法
@RunWith(PowerMockRunner.class) public class UserServiceTest9 { @Test public void testIsSuperUser() throws Exception { Long userId = 1L; boolean expected = false; UserService userService = new UserService(); Method method = PowerMockito.method(UserService.class, "isSuperUser", Long.class); Object actual = method.invoke(userService, userId); Assert.assertEquals("返回值不相等", expected, actual); } } |
8.4. 验证私有方法
@RunWith(PowerMockRunner.class) @PrepareForTest({UserService.class}) public class UserServiceTest10 { @Test public void testIsNotSuperUser() throws Exception { Long userId = 1L; boolean expected = false; UserService userService = PowerMockito.spy(new UserService()); PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected); boolean actual = userService.isNotSuperUser(userId); PowerMockito.verifyPrivate(userService).invoke("isSuperUser", userId); Assert.assertEquals("返回值不相等", expected, actual); } } |
这里,也可以用Method那套方法进行模拟和验证方法。
9. 主要注解
PowerMock为了更好地支持SpringMVC/SpringBoot项目,提供了一系列的注解,大大地简化了测试代码。
9.1. @RunWith注解
@RunWith(PowerMockRunner.class)
指定JUnit 使用 PowerMock 框架中的单元测试运行器。
9.2. @PrepareForTest注解
@PrepareForTest({ TargetClass.class })
当需要模拟final类、final方法或静态方法时,需要添加@PrepareForTest注解,并指定方法所在的类。如果需要指定多个类,在{}中添加多个类并用逗号隔开即可。
9.3. @Mock注解
@Mock注解创建了一个全部Mock的实例,所有属性和方法全被置空(0或者null)。
9.4. @Spy注解
@Spy注解创建了一个没有Mock的实例,所有成员方法都会按照原方法的逻辑执行,直到被Mock返回某个具体的值为止。
注意:@Spy注解的变量需要被初始化,否则执行时会抛出异常。
9.5. @InjectMocks注解
@InjectMocks注解创建一个实例,这个实例可以调用真实代码的方法,其余用@Mock或@Spy注解创建的实例将被注入到用该实例中。
@Service public class UserService { @Autowired private UserDAO userDAO; public void modifyUser(UserVO userVO) { UserDO userDO = new UserDO(); BeanUtils.copyProperties(userVO, userDO); userDAO.modify(userDO); } } @RunWith(PowerMockRunner.class) public class UserServiceTest { @Mock private UserDAO userDAO; @InjectMocks private UserService userService; @Test public void testCreateUser() { UserVO userVO = new UserVO(); userVO.setId(1L); userVO.setName("changyi"); userVO.setDesc("test user"); userService.modifyUser(userVO); ArgumentCaptor<UserDO> argumentCaptor = ArgumentCaptor.forClass(UserDO.class); Mockito.verify(userDAO).modify(argumentCaptor.capture()); UserDO userDO = argumentCaptor.getValue(); Assert.assertNotNull("用户实例为空", userDO); Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId()); Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName()); Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc()); } } |
9.6. @Captor注解
@Captor注解在字段级别创建参数捕获器。但是,在测试方法启动前,必须调用MockitoAnnotations.openMocks(this)进行初始化。@Service
public class UserService { @Autowired private UserDAO userDAO; public void modifyUser(UserVO userVO) { UserDO userDO = new UserDO(); BeanUtils.copyProperties(userVO, userDO); userDAO.modify(userDO); } } @RunWith(PowerMockRunner.class) public class UserServiceTest { @Mock private UserDAO userDAO; @InjectMocks private UserService userService; @Captor private ArgumentCaptor<UserDO> argumentCaptor; @Before public void beforeTest() { MockitoAnnotations.openMocks(this); } @Test public void testCreateUser() { UserVO userVO = new UserVO(); userVO.setId(1L); userVO.setName("changyi"); userVO.setDesc("test user"); userService.modifyUser(userVO); Mockito.verify(userDAO).modify(argumentCaptor.capture()); UserDO userDO = argumentCaptor.getValue(); Assert.assertNotNull("用户实例为空", userDO); Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId()); Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName()); Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc()); } } |
9.7. @PowerMockIgnore注解
为了解决使用PowerMock后,提示ClassLoader错误。
10. 相关观点
10.1. 为什么要使用Mock?
根据网络相关资料,总结观点如下:
Mock可以用来解除外部服务依赖,从而保证了测试用例的独立性。
现在的互联网软件系统,通常采用了分布式部署的微服务,为了单元测试某一服务而准备其它服务,存在极大的依耐性和不可行性。
Mock可以减少全链路测试数据准备,从而提高了编写测试用例的速度。
传统的集成测试,需要准备全链路的测试数据,可能某些环节并不是你所熟悉的。最后,耗费了大量的时间和经历,并不一定得到你想要的结果。现在的单元测试,只需要模拟上游的输入数据,并验证给下游的输出数据,编写测试用例并进行测试的速度可以提高很多倍。
Mock可以模拟一些非正常的流程,从而保证了测试用例的代码覆盖率。
根据单元测试的BCDE原则,需要进行边界值测试(Border)和强制错误信息输入(Error),这样有助于覆盖整个代码逻辑。在实际系统中,很难去构造这些边界值,也能难去触发这些错误信息。而Mock从根本上解决了这个问题:想要什么样的边界值,只需要进行Mock;想要什么样的错误信息,也只需要进行Mock。
Mock可以不用加载项目环境配置,从而保证了测试用例的执行速度。
在进行集成测试时,我们需要加载项目的所有环境配置,启动项目依赖的所有服务接口。往往执行一个测试用例,需要几分钟乃至几十分钟。采用Mock实现的测试用例,不用加载项目环境配置,也不依赖其它服务接口,执行速度往往在几秒之内,大大地提高了单元测试的执行速度。
10.2. 单元测试与集成测试的区别
在实际工作中,不少同学用集成测试代替了单元测试,或者认为集成测试就是单元测试。这里,总结为了单元测试与集成测试的区别:
测试对象不同
单元测试对象是实现了具体功能的程序单元,集成测试对象是概要设计规划中的模块及模块间的组合。
测试方法不同
测试时间不同
集成测试要晚于单元测试。
测试内容不同
单元测试主要是模块内程序的逻辑、功能、参数传递、变量引用、出错处理及需求和设计中具体要求方面的测试;而集成测试主要验证各个接口、接口之间的数据传递关系,及模块组合后能否达到预期效果。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理