单元测试工具-PowerMock
Mock:为某个类创建一个虚假的对象,在测试时用这个虚假的对象替换掉真实的对象。
PowerMock实现原理:
1)PowerMock会创建一个org.powermock.core.classloader.MockClassLoader类加载器,然后该类加载器会去加载测试用例使用到的类(系统类除外)。
2)PowerMock会去修改写在注解@PrepareForTest里的class文件(注:当前测试类会自动加入该注解中),以实现mock的功能。
3)如果需要mock系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件。
UT指标:
行覆盖、函数覆盖、分支覆盖
增量行覆盖、增量函数覆盖、增量分支覆盖
注意:AClassTest和AClass的包路径必须一致,否则单测覆盖率统计不到。
实现:
第一步:
maven依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
第二步:
添加注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class UserServiceImplTest {
@InjectMocks
private ClassA clazzA;
@Mock
private ClassB clazzB;
@Mock
private ClassC clazzC;
// 实例化@InjectMocks注解修饰的属性!
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testMethodA() {
// Mock一般方法:
List myList = new ArrayList();
PowerMockito.when(clazzA.methodA(Mockito.anyInt(), Mockito.anyString(), Mockito.anyObject()).thenReturn(myList);
List result = clazzA.methodA(17, "test", new ArrayList());
Assert.assertEquals(myList, result);
}
}
说明:
@RunWith(PowerMockRunner.class)注解:表示使用PowerMockerRunner来运行测试用例,否则无法使用powermock。
@PrepareForTest({xx.class, xxx.class})注解:声明需要测试的类(即:要测试的目标方法所在的类 + 目标方法中使用到的且需要mock的其它类),以逗号分隔。
注意:
1>普通Mock(mock一般方法)不需要加@RunWith(PowerMockRunner.class)和@PrepareForTest({xx.class, xxx.class})注解!
2>非普通mock(mock静态方法、私有方法、构造方法、final方法、抽象方法)必须加@RunWith(PowerMockRunner.class)和@PrepareForTest({xx.class, xxx.class})注解!
@InjectMocks注解:Mockito会将@Mock或@Spy修饰的属性自动注入到@InjectMocks修饰的属性中。
@Mock注解:为某个类创建一个虚假的对象,在测试时用这个虚假的对象替换掉真实的对象。
@Spy注解:
1>当一个对象中的部分方法需要mock,部分方法需要真实调用的时候,我们可以使用该注解来解决这个问题。
2>被@spy修饰的对象:
若方法已经打桩(打桩方式有特定要求,不是所有形式的桩都可以),则走打桩逻辑;否则走正常调用逻辑。
特别注意:
Mockito.doReturn(obj).when(xxxService).methodxx(anyObject())); // 这种打桩方式有效,打桩后会走mock逻辑。
Mockito.when(xxxService.methodxx(anyObject())).thenReturn(obj); // 这种打桩方式无效,打桩后仍然会走正常调用逻辑!
3>被@spy修饰的对象:属性不会自动注入,可以通过反射来手动赋值。eg:MyTransactionalTest
要想使以上注解生效,则必须在执行测试方法前 先实例化@InjectMocks注解修饰的属性:MockitoAnnotations.initMocks(this);
Mock方法:
注意:如果方法的参数不是mock出来的,则调用方法的实例就不会是mock出来的了!!!
Mock一般方法:
PowerMockito.when(clazzA.methodA(Mockito.anyObject())).thenReturn("ok");
PowerMockito.doThrow(new XxxException("")).when(clazzA).methodA(Mockito.any(XXX.class))
注意:doThrow中抛出异常的范围不能超过methodA方法抛出的异常的范围。若methodA没有显示声明抛出异常,则我们可以thenThrow(new RuntimeException(""))。
Mock静态方法:
PowerMockito.mockStatic(MyUtils.class);
PowerMockito.when(MyUtils.methodA()).thenReturn("ok");
注:若MyUtils的方法为static void ,则默认会mock掉。
Mock私有方法:
PowerMockito.when(clazzA, "methodB").thenReturn("ok");
PowerMockito.when(clazzA, "methodB", param1, param2).thenReturn("ok");
PowerMockito.when(clazzA, "methodB").thenCallRealMethod();
举例:PowerMockito.when(clazzA, "methodB", Mockito.anyObject(), Mockito.anyObject()).thenReturn("ok");
说明:也可以考虑使用反射来修改类的私有属性、调用类的私有方法。
举例:
ReflectionTestUtils.setField(targetObject, "token", "123");
ReflectionTestUtils.invokeMethod(targetObject, "methodName", param1, param2);
Mock构造方法:
说明:当我们需要mock方法内new出来的变量时,我们可以通过mock构造方法来实现。
// mock file对象
PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(new File("aaa"));
// mock file对象并且打桩
File mockFile = Mockito.mock(File.class);
PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(mockFile);
PowerMockito.when(mockFile.canExecute()).thenReturn(false);
// PowerMockito.when(mockFile.canExecute()).thenThrow(new RuntimeException());
Mock抽象方法:
说明:我们一般选择去mock抽象类的实现类,而不是直接mock抽象类。
断言的场景:
断言方法的返回值与期望的返回值相同: Assert.assertEquals(result, expectResult);
断言方法被调用n次
一般用来判断void方法: Mockito.verify(aaaService, Mockito.times(n)).method1(Mockito.anyString(), Mockito.<Function>anyObject(), Mockito.any(Xxx.class));
使用PowerMockito检查某个静态方法调用的次数:
步骤:先调用对应的静态方法,再启用静态检查,最后再调用对应的静态方法。这样的用法比较奇怪,记住即可。
举例:验证Runtime.getRuntime()是否被调用了2次。
PowerMockito.mockStatic(Runtime.class);
Runtime.getRuntime();
Runtime.getRuntime();
// do other things
PowerMockito.verifyStatic(Mockito.times(2));
Runtime.getRuntime();
常见的错误:
打桩时,若mock出来的方法入参(Mockito.anyLong()等方法)无法打桩成功时,将所有的入参替换为调用时的传入真实值即可解决问题。eg:
Mockito.when(xxxService.methodA(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(new ArrayList());
替换为:
Mockito.when(xxxService.methodA(111L, false)).thenReturn(new ArrayList());
org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.
解决:when()方法中的参数要求是一个mock对象的方法调用。报该错误的原因是调用方法的对象不是mock出来的对象。如果是调用静态方法,则使用PowerMockito.mockStatic(类名.class);
org.powermock.api.mockito.ClassNotPreparedException: The class XXX not prepared for test.
解决:将XXX.class添加到@PrepareForTest中。
与其它单测模式混用:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Transactional
@Rollback
public class MyTransactionalTest {
@Resource
private AaaService aaaService;
@Mock
private BbbService bbbService;
@spy
private CccService cccService;
@Resource
private DddService dddService;
@Before
public void initMock() {
MockitoAnnotations.initMocks(this);
// 使用反射,将mock的对象直接注入到指定类中,之后我们就可以对这个mock的对象进行打桩操作了。说明:如果没有对mock的对象进行打桩,则该对象方法的调用走默认的mock逻辑。
ReflectionTestUtils.setField(aaaService, "bbbService", bbbService);
// 使用反射,将spy的对象直接注入到指定类中,之后我们就可以对这个spy的对象进行打桩操作了。说明:如果没有对spy的对象进行打桩,则该对象方法的调用走正常调用逻辑。
ReflectionTestUtils.setField(aaaService, "cccService", cccService);
// 使用反射,给@spy修饰的对象的属性手动赋值。
ReflectionTestUtils.setField(cccService, "dddService", dddService);
}
}
单元测试工具-PowerMock
于 2019-05-12 23:19:56 首次发布