[TOC]
为什么用PowerMock
在写Android单测的时候,大家多为这几件事苦恼:
- 与Android API进行的交互,如何隔绝?
- 如何对private method/field进行mock?
- 如何对网络接口进行mock?
上面这几件事情可以通过PowerMock来进行解决。
PowerMock是什么
PowerMock是一个第三方开源框架,是对Mockito的扩展,主要围绕JUnit及TestN。可以对私有方法,私有属性,final方法进行mock。
本文主要围绕Junit + Mockito + PowerMock 组合进行
PowerMock怎么用
导入工程:
// Mockito
testCompile "org.mockito:mockito-core:2.8.9"
// PowerMock
testCompile "org.powermock:powermock-module-junit4:1.7.4"
testCompile "org.powermock:powermock-module-junit4-rule:1.7.4"
testCompile "org.powermock:powermock-api-mockito2:1.7.4"
testCompile "org.powermock:powermock-classloading-xstream:1.7.4"
PowerMock既然是Mockito的扩展,所以需要导入mockito-core。同时Mockito的版本与PowerMock的版本需要相对对应,具体可以参考PowerMock的Github主页。
mock静态方法/变量
public class MyClass {
private static int privateStaticFiled = 123;
public static int staticReturnMethod() {
return 1;
}
public static void staticVoidMethod() {
throw new IllegalStateException("should not go here");
}
private static void staticPrivateMethod(String a) {
throw new IllegalStateException(a);
}
}
所有的测试方法均在下面的类中进行
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyClassTest {
}
mock非void类型的静态方法
@Test
public void staticReturnMethodTest() throws Exception {
PowerMockito.mockStatic(MyClass.class);
PowerMockito.when(MyClass.staticReturnMethod()).thenReturn(2);
Assert.assertEquals(2, MyClass.staticReturnMethod());
}
mock抛出异常:
PowerMockito.when(MyClass.staticReturnMethod()).thenThrow(new RuntimeException());
mock带参数void类型的静态方法
@Test
public void staticVoidMethodTest() throws Exception {
PowerMockito.mockStatic(MyClass.class);
PowerMockito.doNothing().when(MyClass.class, "staticVoidMethod", Mockito.anyInt());
}
方法计数:
MyClass.staticVoidMethod(123);//此处是对方法的调用
PowerMockito.verifyStatic(MyClass.class, Mockito.times(1));//本行及下面一行是记录方法调用及验证是否调用指定次数
MyClass.staticVoidMethod(123);
方法应答:
可以用来对方法内部逻辑进行mock
PowerMockito.doAnswer(new Answer() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return null;
}
}).when(MyClass.class, "staticVoidMethod", Mockito.anyInt());
mock私有静态变量
Whitebox.setInternalState(myClass,"privateStaticFiled",789);
mock私有方法/变量
public class MyClass {
private int privateFiled = 123;
private int privateReturnMethod() {
return 1;
}
}
测试方法写到如下类中:
@RunWith(PowerMockRunner.class)
public class MyClassTest {
}
带返回值的私有方法
MyClass myClass = new MyClass();
PowerMockito.doReturn(123).when(myClass,"privateReturnMethod");
同时doReturn可以换成doAnswer,doNothing等等
私有属性
Whitebox.setInternalState(myClass,"privateFiled",789);
mock构造方法
public class MyClass{
public boolean newMethod(String path) {
File file = new File(path);
return file.exists();
}
}
对构造方法进行mock
File file = PowerMockito.mock(File.class);
MyClass myClass = new MyClass();
PowerMockito.whenNew(File.class).withArguments("abc").thenReturn(file);
PowerMockito.when(file.exists()).thenReturn(true);
Assert.assertTrue(myClass.newMethod("abc"));
PowerMock原理简单剖析
代替系统的ClassLoader加载Class
PowerMock实现了ClassLoader
java.lang.ClassLoader
|-javassist.Loader
|-org.powermock.core.classloader.DeferSupportingClassLoader
|-org.powermock.core.classloader.MockClassLoader
在MockClassLoaderFactory中被创建
public ClassLoader create() {
final String[] classesToLoadByMockClassloader = makeSureArrayContainsTestClassName(this.classesToLoadByMockClassloader, testClass.getName());
final ClassLoader mockLoader;
if (isContextClassLoaderShouldBeUsed(classesToLoadByMockClassloader)) {//判断是否应该使用MockClassLoader
mockLoader = Thread.currentThread().getContextClassLoader();
} else {
mockLoader = createMockClassLoader(classesToLoadByMockClassloader);
}
return mockLoader;
}
在AbstractCommonTestSuiteChunkerImpl中被调用时:
createNewClassloader(testClass, new String[]{MockClassLoader.MODIFY_ALL_CLASSES},ignorePackagesExtractor.getPackagesToIgnore(testClass), extraTransformers);
然而MockClassLoader.MODIFY_ALL_CLASSES = *可以判断基本会对使用@RunWith(PowerMockRunner.class)注解白标注的测试类使用MockClassLoader加载。
使用javassist修改字节码
在org.powermock.core.transformers包下AbstractMainMockTransformer类及其派生类中动态修改了被测类的字节码,根据测试类中的调用规则,生成了新的字节码文件替代原有字节码文件。
总结
通过上述分析,基本上可以判断PowerMock通过使用MockClassLoader替换系统ClassLoader,在类加载时动态生成了被测类的替代字节码文件。