单测PowerMock
如何写一个单测(过程)
1. 定义被测对象
- 直接构建对象
UserService userService = new UserService();
- 利用Mockito.spy
Mockito提供一个spy功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都是真实方法,除非主动去模拟对应方法。所以,利用spy功能来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。
UserService userService = Mockito.spy(new UserService());
UserService userService = Mockito.spy(UserService.class);
AbstractOssService ossService = Mockito.spy(AbstractOssService.class);
- @spy注解
@Spy注解跟Mockito.spy方法一样,可以用来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。
@RunWith(PowerMockRunner.class)
public class CompanyServiceTest {
@Spy
private UserService userService = new UserService();
...
}
注意:@Spy注解对象需要初始化。如果是虚基类或接口,可以用Mockito.mock方法实例化
- @InjectMocks注解
@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。所以,@InjectMocks注解本身就可以用来定义被测对象。@InjectMocks注解需要配合@RunWith注解使用。
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@InjectMocks
private UserService userService;
...
}
2.模拟依赖对象
- 直接创建
Long userId = 1L;
String userName = "admin";
UserDO user = new User();
user.setId(userId);
user.setName(userName);
List userIdList = Arrays.asList(1L, 2L, 3L);
- 反序列化
- Mockito.mock
拦截尚未实现或不希望被真是调用的方法,默认所有方法已被模拟——方法为空并返回默认值(null/0),除非执行doCallRealMethod或thenCallRealMethod操作才能调用真实方法。
使用情形:
只使用类实例,不使用类属性;
类属性太多,但使用其中少量属性(可以mock属性返回值);
类是接口或虚基类,并不关心其具体实现类。
MockClass mockClass = Mockito.mock(MockClass.class);
List userIdList = (List)Mockito.mock(List.class);
- @Mock注解
@Mock注解跟Mockito.mock方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Mock注解需要配合@RunWith注解使用。
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Mock
private UserDAO userDAO;
...
}
- Mockito.spy
跟Mockito.mock相似,只是Mockito.spy方法默认所有方法都是真实的,除非主动去模拟 - @Spy注解
@Spy注解跟Mockito.spy方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用
注意:@Spy注解对象需要初始化。如果是虚基类或接口,可以用Mockito.mock方法实例化。
3.注入依赖对象
- 利用Setter方法注入
如果类定义了Setter方法,可以直接调用方法设置字段值。
userService.setMaxCount(100);
userService.setUserDAO(userDAO);
- 利用ReflectionTestUtils.setField方法注入
JUnit提供ReflectionTestUtils.setField方法设置属性字段值。
ReflectionTestUtils.setField(userService, "maxCount", 100);
ReflectionTestUtils.setField(userService, "userDAO", userDAO);
- 利用Whitebox.setInternalState方法注入
PowerMock提供Whitebox.setInternalState方法设置属性字段值。
Whitebox.setInternalState(userService, "maxCount", 100);
Whitebox.setInternalState(userService, "userDAO", userDAO);
- 利用@InjectMocks注解注入
@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。@InjectMocks注解需要配合@RunWith注解使用。
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Mock
private UserDAO userDAO;
private Boolean canModify;
@InjectMocks
private UserService userService;
...
}
- 设置静态常量字段值
有时候,我们需要对静态常量对象进行模拟,然后去验证是否执行了对应分支下的方法。比如:需要模拟Lombok的@Slf4j生成的log静态常量。但是,Whitebox.setInternalState方法和@InjectMocks注解并不支持设置静态常量,需要自己实现一个设置静态常量的方法:
public final class FieldHelper {
public static void setStaticFinalField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
FieldUtils.removeFinalModifier(field);
FieldUtils.writeStaticField(field, fieldValue, true);
}
}
具体使用方法如下:
FieldHelper.setStaticFinalField(UserService.class, "log", log);
注意:经过测试,该方法对于int、Integer等基础类型并不生效,应该是编译器常量优化导致。
4.模拟依赖方法
4.1 根据返回模拟方法
4.2 根据参数模拟方法
4.模拟其他方法
5.调用被测方法
需要对返回值和异常进行验证
根据有误访问权限介绍如何调用
有访问权限 | 无访问权限 | |
---|---|---|
构造方法 | 直接调用、Whitebox.invokeConstructor() | Whitebox.invokeConstructor() |
普通方法 | 直接调用、Whitebox.invokeMethod、Whitebox.getMethod、PowerMockito.method、method.invoke | Whitebox.invokeMethod、Whitebox.getMethod、PowerMockito.method、method.invoke |
静态方法 | 直接调用、Whitebox.invokeMethod | Whitebox.invokeMethod |
6.验证依赖方法
对捕获的参数值进行验证
6.1 根据参数验证方法调用
6.2 验证方法调用次数
6.3 验证方法调用并捕获参数值
6.4 验证其他特殊方法
7.验证数据对象
参考:https://mp.weixin.qq.com/s/hX_RIYs-nBnqVwdq5B4rhg