一、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单元测试
好的单测习惯,可以促使你写出更易于测试的代码~~