使用PowerMock写Android单元测试

[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,在类加载时动态生成了被测类的替代字节码文件。

你可能感兴趣的:(使用PowerMock写Android单元测试)