一、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 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[] 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 demoAnswer =
new
Answer() {
@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 demoAnswer =
new
Answer() {
@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[] 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单元测试
好的单测习惯,可以促使你写出更易于测试的代码~~