一、Mock的使用背景
单元测试的思路就是我们想在不涉及依赖关系的情况下测试代码。
在单元测试中,我们往往想去独立地去测一个类中的某个方法,但是这个类可不是独立的,它会去调用一些其它类的方法和service,这也就导致了以下两个问题:
- 外部服务可能无法在单元测试的环境中正常工作,因为它们可能需要访问数据库或者调用其它Http服务。
- 我们的测试关注点在于这个类的实现上,外部类的一些行为可能会影响到我们对本类的测试,那也就失去了我们进行单测的意义。
先来看看下面这个示例:我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是将依赖mock掉, 把测试的重心完全放在目标类A上

Mock测试就是在测试过程中,对那些不容易构建的对象,用一个虚拟对象来代替测试的情形。
说白了:就是解耦(虚拟化)要测试的目标方法中调用的其它方法,例如:Service的方法调用Mapper类的方法,这时候就要把Mapper类Mock掉(产生一个虚拟对象),
这样我们可以自由的控制这个Mapper类中的方法,让它们返回想要的结果、抛出指定异常、验证方法的调用次数等等。
二、常用Mock框架
Mockito 和 PowerMock
Mockito是一个优秀的、最常用的单元测试mock框架,它能满足大部分时间的测试要求(public方法);
PowerMock可以去解决一些更难的问题(比如静态方法、私有方法、Final方法等)。
PowerMock 是在 EasyMock 以及 Mockito 基础上的扩展,通过提供定制的类加载器以及一些字节码篡改,实现更强大的测试功能。
三、Mockito的使用实例
1、测试类初始化
测试目标类:

初始化方式有两种:
(1)注解

@RunWith(MockitoJUnitRunner.class) 换成@RunWith(PowerMockRunner.class)也可以支持这些注解。
@Mock相当于:NeedMockClass mockInstatnce = Mockito.mock(NeedMockClass.class); 还有一种@Spy后面提到,等价于NeedMockClass spyInstatnce = Mockito.spy(new NeedMockClass());
被测试类上标记@InjectMocks,Mockito就会实例化该类,并将标记@Mock、@Spy注解的属性值注入到被测试类中。
注意@InjectMocks的注入顺序:
- 如果这里的TargetClass中没有显示定义构造方法,Mockito会调用默认构造函数实例化对象,然后依次寻找setter 方法 或 属性(按Mock对象的类型或名称匹配)注入@Mock对象;
- 如果TargetClass中显式定义了有参数的构造函数,那么 就不再寻找setter 方法和 属性注入, Mockito会选择参数个数最多的构造函数实例化并注入@Mock对象(这样可以尽可能注入多的属性);
但是有多个最大构造函数时,Mockito 究竟选择哪一个就混乱了,测试时应该避免这种情况的发生,很容易发生空指针。

如上图:此时invokeService 和 invokeMapper肯定有一个是null
(2)类反射

2、Mockito实例
public方法测试实例
?
测试目标类:
@Service
public class MsgLogService implements IMsgLogService {
@Resource
private MsgLogMapper msgLogMapper;
@Resource
private IZhwxApiService zhwxApiService;
@Override
public void handle() {
List<MsgLog> list = msgLogMapper.selectUnHandledMsg();
if (CollectionUtils.isEmpty(list)) {
return ;
}
for (MsgLog log : list) {
if (StringUtils.isEmpty(log.getMsg())) {
continue ;
}
boolean res = zhwxApiService.putProfileToHBase(log.getMsg());
if (res) {
msgLogMapper.updateHandleStatus(log);
}
}
}
}
测试类:
@RunWith (MockitoJUnitRunner. class )
public class MsgLogServiceTest {
@InjectMocks
private MsgLogService msgLogService;
@Mock
private MsgLogMapper msgLogMapper;
@Mock
private IZhwxApiService zhwxApiService;
@Test
public void handle_request_times_0_01() {
Mockito.when(msgLogMapper.selectUnHandledMsg()).thenReturn( null );
msgLogService.handle();
Mockito.verify(zhwxApiService, Mockito.times( 0 )).putProfileToHBase(Mockito.anyString());
Mockito.verify(msgLogMapper, Mockito.times( 0 )).updateHandleStatus(Mockito.any());
}
}
注:when()、any()、verify()等都是Mockito类中的静态方法,推荐将这些静态方法导入,就可以在测试类中直接引用了
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Matchers.any;
|
3、Mock的发生时机: Stubbing
当以下语法出现时,Mock就发生了,此时称作设置测试桩(Stubbing)
?
when(mockMapper.insert(any())).thenReturn( 888 );
when(mockMapper.insert(any())).thenThrow( new RuntimeException( "db操作异常" ));
when(mockService.methodReturnId(any(OrderInfo. class ))).thenAnswer(demoAnswer);
doReturn( 888 ).when(mockMapper).insert(any());
|
上面mock的方法都是有返回值的,为void函数设置桩用以下语法,因为编译器不喜欢void函数在括号内
?
doNothing().when(mockService).voidMethod(any(String. class ), any(Integer. class ));
doThrow( new RuntimeException( "" )).when(mockService).voidMethod(eq( "ex" ), eq( 10001 ));
doAnswer(demoAnswer).when(mockService).methodVoid(any(OrderInfo. class ));
|
Stubbing连缀调用:
?
第一次调用返回 1 ,第二次调用返回 2 ,以下三种写法等价的:
when(mockService.addStr(anyString())).thenReturn( "1" ).thenReturn( "2" );
when(mockService.addStr(anyString())).thenReturn( "1" , "2" );
doReturn( "1" ).doReturn( "2" ).when(mockService).addStr(anyString());
String relt1 = mockService.addStr( "x" );
String relt2 = mockService.addStr( "x" );
String relt3 = mockService.addStr( "x" );
Assert.assertEquals( "1" , relt1);
Assert.assertEquals( "2" , relt2);
Assert.assertEquals( "2" , relt3); //后续调用一直返回2
第一次调用什么也不做,第二次调用抛出异常:
doNothing().doThrow( new RuntimeException( "调用两次了" )).when(mockService).methodVoid(any());
mockService.methodVoid(any());
try {
mockService.methodVoid(any());
} catch (Exception e) {
Assert.assertEquals( "调用两次了" , e.getMessage());
}
下面写法结果就变了,第二次stubbing覆盖第一次的:
when(mockService.addStr(anyString())).thenReturn( "1" );
when(mockService.addStr(anyString())).thenReturn( "2" );
String relt1 = mockService.addStr( "x" );
String relt2 = mockService.addStr( "x" );
Assert.assertEquals( "2" , relt1);
Assert.assertEquals( "2" , relt2);
|
4、Mock方法的默认值
Mock对象的方法未设置测试桩时,Mockito会返回方法返回类型的默认值,不会报错。mock 实例默认的会给所有的方法添加基本实现:返回 null 或空集合,或者 0 等基本类型的值。这取决于方法返回类型
?
List mockList = mock(List. class );
when(mockList.get( 5 )).thenReturn( "hello" ); //打桩
Assert.assertEquals( "hello" , mockList.get( 5 )); //打桩的情景返回设定值
Assert.assertEquals( null , mockList.get( 10 )); //未打桩的情景不会报错,返回默认值
|
5、参数匹配器(matchers)
是一些静态方法,说白了就是mock方法成立的一些条件
列举几个典型的匹配器:
any() : 任何参数
any(OrderInfo.class): 任何OrderInfo(开发中自定义的类)
anyString() :任何字符串,等同于any(String.class)
eq(1): 具体值1
?
InvokeService类中,要被mock的方法
public Integer targetMethod01(String param01, Integer param02) {
return 1 ;
}
测试类TargetClassTest中:
@Mock
private InvokeService mockService;
@Test
public void dmeoTest() {
when(mockService.targetMethod01(any(), any())).thenReturn( 666 );
when(mockService.targetMethod01(any(String. class ), anyInt())).thenReturn( 666 );
when(mockService.targetMethod01( "demo" , 1 )).thenReturn( 666 );
when(mockService.targetMethod01(eq( "demo" ), eq( 1 ))).thenReturn( 666 );
//上面都是正确的,下面的写法,单测执行时会报错
when(mockService.targetMethod01(eq( "demo" ), 1 )).thenReturn( 666 );
}
一旦使用了参数匹配器来验证,那么所有参数都应该使用参数匹配
|
6、行为测试
前面提到的 when(……).thenReturn(……) 等属于状态测试,某些时候,测试不关心返回结果,而是侧重方法有否被正确的参数调用过。从概念上讲,就是和状态测试所不同的“行为测试”了。
一旦使用 mock() 或@Mock生成模拟对象,意味着 Mockito 会记录着这个模拟对象调用了什么方法,还有调用了多少次、调用的顺序等。最后由用户决定是否需要进行验证。
6-1、验证Mock方法的调用次数
几个常用的方法:
times(x):mock方法被调用x次
never():从未被调用过,等价于times(0)
atLeast(x):至少调用过x次
atLeastOnce():至少调用过1次,等价于atLeast(1)
atMost(x):最多调用过x次
verify 内部跟踪了所有的方法调用和参数的调用情况,然后会返回一个结果,说明是否通过。
verify 也可以像 when 那样使用参数匹配器。
?
目标类TargetClass中:
@Autowired
private InvokeService invokeService;
public void invokeTimes() {
invokeService.addStr( "1" );
invokeService.addStr( "2" );
invokeService.addStr( "2" );
}
测试类TargetClassTest中:
@InjectMocks
TargetClass targetClass;
@Mock
private InvokeService mockService;
@Test
public void testTimes() {
targetClass.invokeTimes();
verify(mockService).addStr(eq( "1" )); //times()省略的话,默认验证调用一次
verify(mockService, times( 1 )).addStr(eq( "1" ));
verify(mockService, times( 1 )).addStr( "1" );
verify(mockService, times( 2 )).addStr( "2" );
verify(mockService, atLeastOnce()).addStr( "1" );
verify(mockService, atMost( 2 )).addStr( "2" );
verify(mockService, never()).addStr( "0" );
}
|
6-2、验证Mock方法的调用顺序
?
目标类TargetClass中:
@Autowired
private InvokeService invokeService;
public void addStr() {
invokeService.add( "str01" );
invokeService.add( "str02" );
}
测试类TargetClassTest中:
@InjectMocks
TargetClass targetClass;
@Mock
private InvokeService mockService;
@Test
public void testInOrder_verify() {
InOrder inOrder = inOrder(mockService);
targetClass.addStr();
//验证调用顺序,若是调换两句,将会出错
inOrder.verify(mockService).add( "str01" );
inOrder.verify(mockService).add( "str02" );
}
|
7、Answer
主要用来截获传递给mock方法的参数
典例:保存一个订单,调用mapper的insert( )方法,保存到数据库的OrderInfo是测试目标方法saveOrder( )中的一个局部变量,
怎样获取到这个OrderInfo,从而验证插入数据库的字段赋值是否正确?
Answer获取方法参数实例
?
目标类TargetClass中:
@Autowired
private InvokeService invokeService;
@Autowired
private InvokeMapper invokeMapper;
public int saveOrder() {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setUserName( "sxd" );
orderInfo.setDealerId( 62669 );
orderInfo.setOrderType( 3 );
int result = invokeMapper.insert(orderInfo);
return result;
}
InvokeMapper中:
int insert(OrderInfo orderInfo);
测试类TargetClassTest中:
@InjectMocks
TargetClass targetClass;
@Mock
private InvokeService mockService;
@Mock
private InvokeMapper mockMapper;
//定义一个Answer类,用于截获mock方法入参
class DemoAnswer implements Answer<Object> {
Object[] params;
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
params = invocationOnMock.getArguments(); //方法入参
return 666 ; //方法返回值
}
public Object[] getParams() {
return params;
}
}
@Test
public void test_insertOrder_param_by_Answer() throws Exception {
DemoAnswer demoAnswer = new DemoAnswer();
doAnswer(demoAnswer).when(mockMapper).insert(any(OrderInfo. class ));
int result = targetClass.saveOrder();
Assert.assertEquals( 666 , result);
OrderInfo saveOrder = (OrderInfo) demoAnswer.getParams()[ 0 ];
Assert.assertEquals( 3 , saveOrder.getOrderType().intValue());
}
|
有返回值的方法demo
?
InvokeService类中,需要mock的方法:
public Integer methodReturnId(OrderInfo orderInfo) throws Exception {
return orderInfo.getId();
}
测试类TargetClassTest中:
@Mock
private InvokeService mockService;
@Test
public void testAnswer_when_method_unvoid() throws Exception {
Answer<Integer> demoAnswer = new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
OrderInfo orderInfo = (OrderInfo) invocationOnMock.getArguments()[ 0 ]; //methodReturnId方法的入参
//orderInfo.setId(2);//截获并自定义入参
if (orderInfo.getId() > 3 ) {
throw new RuntimeException( "大于3了" );
}
return 666 ; //自定义methodReturnId方法的返回值
}
};
//mock的方法是非void时,以下三种写法都可以
//doAnswer(answer).when(mockService).methodReturnId(any(OrderInfo.class));//doAnswer:执行demoAnswer的answer方法
//when(mockService.methodReturnId(any(OrderInfo.class))).then(demoAnswer);//then(answer): 执行demoAnswer的answer方法
when(mockService.methodReturnId(any(OrderInfo. class ))).thenAnswer(demoAnswer); //thenAnswer(answer): 执行demoAnswer的answer方法
OrderInfo paramOrder = new OrderInfo() {{
setId( 3 );
}};
Integer result = mockService.methodReturnId(paramOrder);
Assert.assertEquals( 666 , result.intValue());
OrderInfo paramOrder02 = new OrderInfo() {{
setId( 4 );
}};
try {
mockService.methodReturnId(paramOrder02);
} catch (Exception e) {
Assert.assertEquals( "大于3了" , e.getMessage());
}
}
|
void方法demo
?
InvokeService中,需要mock的方法:
public void methodVoid(OrderInfo orderInfo) {
}
测试类中:
@Mock
private InvokeService mockService;
@Test
public void testAnswer_when_method_void() throws Exception {
Answer<Integer> demoAnswer = new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
OrderInfo orderInfo = (OrderInfo) invocationOnMock.getArguments()[ 0 ]; //methodReturnId方法的入参
//orderInfo.setId(2);
if (orderInfo.getId() > 3 ) {
throw new RuntimeException( "大于3了" );
}
return null ;
}
};
//void方法时,只能这样写
doAnswer(demoAnswer).when(mockService).methodVoid(any(OrderInfo. class )); //doAnswer:执行demoAnswer的answer方法
mockService.methodVoid( new OrderInfo() {{
setId( 3 );
}});
}
|
8、void方法的mock
对void方法进行方法预期设定
?
doNothing().when(mockService).voidMethod(anyString(), any(Integer. class )); //什么也不做
doThrow( new RuntimeException( "" )).when(mockService).voidMethod(eq( "ex" ), eq( 10001 )); //抛出指定异常
doNothing().doThrow( new RuntimeException( "" )).when(mockService).voidMethod(anyString(), any(Integer. class )); //第一次调用什么也不做,第二次调用抛异常
|
四、PowerMock的使用实例
1、PowerMock简单实现原理
当某个类被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。
PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
PowerMock支持Mockito
PowerMock有两个重要的注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
2、基本用法
只是需要调用mock对象的public方法,其实此时使用Mockito就可以了
?
目标类:
public class TargetClass {
@Autowired
private InvokeService invokeService;
@Autowired
private InvokeMapper invokeMapper;
public TargetClass() {
}
public TargetClass(InvokeMapper invokeMapper) {
this .invokeMapper = invokeMapper;
}
public int saveToDb() {
return invokeMapper.insert( new OrderInfo());
}
}
测试类:
//@RunWith(PowerMockRunner.class) //未mock静态、私有、final等方法时可以不需要Runner
public class TargetClassPowerMockTest {
@Test
public void test_common_method() {
//PowerMock中生成mock对象
InvokeMapper mockMapper = PowerMockito.mock(InvokeMapper. class );
PowerMockito.when(mockMapper.insert(Mockito.any())).thenReturn( 666 ); //支持Mockito中的参数匹配器
TargetClass targetClass = new TargetClass(mockMapper);
int result = targetClass.saveToDb();
Assert.assertEquals( 666 , result);
}
}
也可以结合使用注解:
@RunWith (PowerMockRunner. class )
public class TargetClassPowerMockTest {
@InjectMocks
TargetClass targetClass;
@Mock
private InvokeService mockService;
@Mock
private InvokeMapper mockMapper;
@Test
public void test_common_method_use_annotation() {
PowerMockito.when(mockMapper.insert(Mockito.any())).thenReturn( 666 ); //支持Mockito中的参数匹配器
int result = targetClass.saveToDb();
Assert.assertEquals( 666 , result);
}
}
|
3、Mock静态方法
测试类必须启用 @PrepareForTest 和 @RunWith注解,否则运行报错
?
目标类:
public class TargetClass {
public String getImgUrl() {
return InvokeClass.getUrl(); //调用静态方法
}
}
静态方法所在的类:
public class InvokeClass {
public static String getUrl() {
return "" ;
}
}
测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({InvokeClass. class }) //注解里InvokeClass是静态方法所在的类
public class TargetClassPowerMockTest {
/**
* mock静态方法
*/
@Test
public void test_static_method() throws Exception {
PowerMockito.mockStatic(InvokeClass. class ); //mock静态方法所在的类
PowerMockito.when(InvokeClass.getUrl()).thenReturn( "www.abc.com" ); //打桩静态方法返回值
TargetClass targetClass = new TargetClass();
String url = targetClass.getImgUrl();
/*验证静态方法调用次数,两行代码要成对出现*/
PowerMockito.verifyStatic(); //验证静态方法被调用一次
InvokeClass.getUrl(); //上面验证次数的静态方法
PowerMockito.verifyStatic(Mockito.times( 1 )); //验证静态方法被调用一次
InvokeClass.getUrl(); //上面验证次数的静态方法
Assert.assertEquals( "www.abc.com" , url);
}
}
|
4、私有方法
4-1 Mock私有方法
测试类必须启用 @PrepareForTest 和 @RunWith注解,否则运行报错
?
目标类TargetClass中:
public Integer callPrivateMethod(Integer param) {
return isAPrivateMethod( new OrderInfo()); //调用私有方法
}
private Integer isAPrivateMethod(OrderInfo order) {
if (order == null ) {
return 111 ;
}
int result = invokeMapper.insert(order);
if (result == 1 ) {
return 666 ;
} else {
return 222 ;
}
}
测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({TargetClass. class }) //注解里TargetClass是私有方法所在的类
public class TargetClassPowerMockTest {
/**
* mock私有方法
* 全部mock
*/
@Test
public void test_private_method_use_mock() throws Exception{
TargetClass mockTargetClass = PowerMockito.mock(TargetClass. class ); //mock掉了TargetClass的所有方法,而我们希望的只是mock被调用的私有方法
PowerMockito.when(mockTargetClass.callPrivateMethod(Mockito.anyInt())).thenCallRealMethod(); //设置callPrivateMethod不被mock,走真实的方法体;否则返回的是对应类型的默认值
PowerMockito.when(mockTargetClass, "isAPrivateMethod" , Mockito.any()).thenReturn( 888 ); //打桩私有方法isAPrivateMethod
Assert.assertEquals( 888 , mockTargetClass.callPrivateMethod( 1 ).intValue());
PowerMockito.verifyPrivate(mockTargetClass, Mockito.times( 1 )).invoke( "isAPrivateMethod" , Mockito.any()); //验证调用了1次该私有方法
}
/**
* mock私有方法
* 部分mock
*/
@Test
public void test_private_method_use_spy() throws Exception{
//为TargetClass创建一个监控(spy)对象,该对象中未设置测试桩的方法,仍然走真实的方法体
TargetClass spyTargetClass = PowerMockito.spy( new TargetClass());
//当给spy的类方法设桩时,最好使用doReturn等,使用thenReturn等可能会产生一些错误
PowerMockito.doReturn( 888 ).when(spyTargetClass, "isAPrivateMethod" , Mockito.any()); //打桩私有方法
Assert.assertEquals( 888 , spyTargetClass.callPrivateMethod( 1 ).intValue());
PowerMockito.verifyPrivate(spyTargetClass, Mockito.times( 1 )).invoke( "isAPrivateMethod" , Mockito.any()); //验证调用了1次该私有方法
}
}
|
mock: 全部mock, 所有没有调用when设置过的方法,在测试时调用,返回的都是对应返回类型的默认值。
spy:部分mock, 意思是你可以修改某个真实对象的某些方法的行为特征,而不改变他的基本行为特征, 只是设桩(when设置)的方法被mock,未设桩的方法走真实逻辑。
4-2 同时mock静态和私有方法
测试类必须启用 @PrepareForTest 和 @RunWith注解,否则运行报错
?
目标类TargetClass中:
public Integer callStaticAndPrivateMethod() {
String url = InvokeClass.getUrl(); //调用静态方法
OrderInfo orderInfo = new OrderInfo();
orderInfo.setIp(url);
Integer result = isAPrivateMethod(orderInfo); //调用私有方法
result = isAPrivateMethod(orderInfo); //调用私有方法
return result;
}
private Integer isAPrivateMethod(OrderInfo order) {
if (order == null ) {
return 111 ;
}
int result = invokeMapper.insert(order);
if (result == 1 ) {
return 666 ;
} else {
return 222 ;
}
}
静态方法所在的类InvokeClass中:
public static String getUrl() {
return "" ;
}
测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({InvokeClass. class , TargetClass. class }) //注解里InvokeClass是静态方法所在的类,TargetClass是私有方法所在的类
public class TargetClassPowerMockTest {
/**
* 同时mock静态、私有方法
*/
@Test
public void test_staticAndPrivate_method() throws Exception {
PowerMockito.mockStatic(InvokeClass. class ); //mock静态方法所在的类
PowerMockito.when(InvokeClass.getUrl()).thenReturn( "www.autohome.com" ); //打桩静态方法返回值
TargetClass spyTargetClass = PowerMockito.spy( new TargetClass());
PowerMockito.doReturn( 888 ).when(spyTargetClass, "isAPrivateMethod" , Mockito.any()); //打桩私有方法
DemoAnswer demoAnswer = new DemoAnswer();
PowerMockito.doAnswer(demoAnswer).when(spyTargetClass, "isAPrivateMethod" , Mockito.any()); //截获私有方法参数
Integer result = spyTargetClass.callStaticAndPrivateMethod(); //调用目标方法
Assert.assertEquals( 666 , result.intValue()); //验证方法返回值
PowerMockito.verifyPrivate(spyTargetClass, Mockito.times( 2 )).invoke( "isAPrivateMethod" , Mockito.any()); //验证调用了2次该私有方法
OrderInfo saveOrder = (OrderInfo) demoAnswer.getParams()[ 0 ]; //截获参数
Assert.assertEquals( "www.autohome.com" , saveOrder.getIp());
}
class DemoAnswer implements Answer<Object> {
Object[] params;
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
params = invocationOnMock.getArguments(); //方法入参
return 666 ; //方法返回值
}
public Object[] getParams() {
return params;
}
}
}
|
4-3 测试私有方法逻辑
使用类反射原理,Mockito就可以实现,无需PowerMock
?
目标类TargetClass中:
public Integer callPrivateMethod(Integer param) {
return isAPrivateMethod( new OrderInfo()); //调用私有方法
}
private Integer isAPrivateMethod(OrderInfo order) {
if (order == null ) {
return 111 ;
}
int result = invokeMapper.insert(order);
if (result == 1 ) {
return 666 ;
} else {
return 222 ;
}
}
测试类中:
/**
* 测试私有方法逻辑
*/
@Test
public void test_private_method_Logic() throws Exception {
InvokeMapper mockMapper = Mockito.mock(InvokeMapper. class );
TargetClass targetClass = new TargetClass(mockMapper);
Method privateMethod =targetClass.getClass().getDeclaredMethod( "isAPrivateMethod" , OrderInfo. class );
privateMethod.setAccessible( true );
OrderInfo orderInfo = null ;
Integer result01 = (Integer) privateMethod.invoke(targetClass, orderInfo);
Assert.assertEquals( 111 , result01.intValue());
orderInfo = new OrderInfo();
Mockito.when(mockMapper.insert(Mockito.any())).thenReturn( 1 );
Integer result02 = (Integer) privateMethod.invoke(targetClass, orderInfo);
Assert.assertEquals( 666 , result02.intValue());
}
|
5、Mock final方法
文档中说测试类必须启用 @PrepareForTest 和 @RunWith注解,但在实际操作中,不加这两个注解仍然可以mock成功,而且使用Mockito也可以mock出final方法
?
目标类:
public class TargetClass {
public String getAppId(InvokeClass invokeClass) {
return invokeClass.getAppId(); //调用final方法
}
}
final 方法所在的类:
public class InvokeClass {
public final String getAppId() {
return "dealer" ;
}
}
PowerMock测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({InvokeClass. class }) //注解里InvokeClass是final方法所在的类, 但此处两个注解可不加
public class TargetClassPowerMockTest {
/**
* final方法
*/
@Test
public void test_final_method() throws Exception {
InvokeClass mockInvokeClass = PowerMockito.mock(InvokeClass. class ); //mock final方法所在的类
PowerMockito.when(mockInvokeClass.getAppId()).thenReturn( "dealercloud" ); //打桩,改写final方法的返回值
TargetClass targetClass = new TargetClass();
String appId = targetClass.getAppId(mockInvokeClass);
Assert.assertEquals( "dealercloud" , appId);
}
}
Mokito测试类:
@RunWith (MockitoJUnitRunner. class )
public class TargetClassTest {
@InjectMocks
TargetClass targetClass;
@Test
public void test_final_method() {
InvokeClass mockInvokeClass = Mockito.mock(InvokeClass. class ); //mock final方法所在的类
Mockito.when(mockInvokeClass.getAppId()).thenReturn( "dc" ); //打桩,改写final方法的返回值
String appId = targetClass.getAppId(mockInvokeClass);
Assert.assertEquals( "dc" , appId);
}
}
|
6、Mock系统类的静态方法
测试类必须启用 @PrepareForTest 和 @RunWith注解,否则运行报错。
和Mock普通类的静态方法、final方法一样,只不过注解@PrepareForTest里写的类不一样 ,是需要调用系统方法所在的类。
?
目标类TargetClass中:
public String callSysStaticMethod(String param) {
return System.getProperty(param); //调用系统类的静态方法
}
测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({TargetClass. class }) //注解里TargetClass是调用系统类静态方法的类
public class TargetClassPowerMockTest {
/**
* mock系统类的静态方法
*/
@Test
public void test_sysStatic_method() {
PowerMockito.mockStatic(System. class ); //mock系统类
PowerMockito.when(System.getProperty( "hello" )).thenReturn( "autohome" ); //打桩系统类的静态方法
TargetClass targetClass = new TargetClass();
String result = targetClass.callSysStaticMethod( "hello" );
Assert.assertEquals( "autohome" , result);
}
}
|
7、Mock方法内局部对象(构造方法)
测试类必须启用 @PrepareForTest 和 @RunWith注解,否则PowerMockito.whenNew( )。。。不生效
?
目标类:
public class TargetClass {
public boolean chekFileExist(String path) {
File file = new File(path);
return file.exists();
}
}
测试类:
@RunWith (PowerMockRunner. class )
@PrepareForTest ({TargetClass. class }) //注解里TargetClass是mock的new对象代码所在的类
public class TargetClassPowerMockTest {
/**
* mock局部对象
*/
@Test
public void test_when_new_inMethod() throws Exception {
File mockFile = PowerMockito.mock(File. class ); //mock出File对象
PowerMockito.when(mockFile.exists()).thenReturn( true );
PowerMockito.whenNew(File. class ).withArguments( "mypath" ).thenReturn(mockFile); //方法内部new出的对象指向mockFile
TargetClass targetClass = new TargetClass();
boolean result = targetClass.chekFileExist( "mypath" );
Assert.assertTrue(result);
}
}
|
五、总结
Mockito和PowerMock使用语法有很大类似, 以上实例基本能覆盖大部分测试要求,具体根据测试需求,优先使用Mockito框架,
并且实际测试中,测试方法命名要规范,单纯的从方法名就能看出这个测试的目的(入参、结果),不要担心因此造成的命名过长。
由于mock测试主要侧重于代码逻辑的测试,对于DAO层覆盖不到,所以方法中用到的sql语句要自己手动执行一遍,起码能保证没有语法错误。
最后附上其它同事的类似总结wiki:
我是这样写Java单元测试的
JAVA单元测试
好的单测习惯,可以促使你写出更易于测试的代码~~
所有评论(0)