PowerMock的简介
编写单元测试仅靠Mockito是不够。因为Mockito无法mock私有方法、final方法及静态方法等。
PowerMock这个framework,主要是为了扩展其他mock框架,如Mockito、EasyMock。它使用一个自定义的类加载器,纂改字节码,突破Mockito无法mock静态方法、构造方法、final类、final方法以及私有方法的限制。
不过,听起来很牛逼。但很多时候你也可以不用它。有一种简单的方法,可以替代它,比如,你可以提高那些成员变量、成员方法的可见性,然后,用@VisibleForTesting
标注该变量或方法。对,就这么简单。android源码也是这么干的。你可以在源码里看见不少这样的注解。
不过如果你要mock的是一些静态方法或者第三方库的私有方法,那只能含泪使用PowerMock了。
PowerMock的集成
1.app的build.gradle下添加依赖
testImplementation "org.powermock:powermock-core:2.0.2"
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
//配合mockito使用需要添加
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
注意:在使用PowerMock作为对Mockito的测试补充框架时,PowerMock的版本,要与使用的Mockito的版本相对应。

PowerMock的使用
1.mock变量
PowerMock可以mock变量。并不是什么黑科技,其实就是通过Java反射修改变量。PowerMock只是对Java反射进行了封装,提供相应的API,方便我们使用。相应的方法,就在WhiteBox
这个类里,下面列出两个经常使用的方法:
//反射修改某个变量的值。修改静态变量的值时,第一个参数传相应的Class即可。
public static void setInternalState(Object object, String fieldName, Object value)
//反射获取某个变量的值。获取静态变量的值时,第二个参数传相应的Class即可。
public static Object getFieldValue(final Field field, final Object object)
注意:PowerMock既然是通过反射修改对应的值,那么就意味着无法对被final修饰的基本类型和String类型变量进行mock(注意:不包括基本类型的包装类型)。至于为什么,文末也会对这个问题进行探讨。
2.mock方法
WhiteBox里面也封装了相应的方法,方便我们通过反射调用私有方法。当然,这不是重点,只是充下字数。

//用于反射调用成员方法
public static synchronized <T> T invokeMethod(Object instance, String methodToExecute, Object... arguments)
//用于反射调用静态方法
public static synchronized <T> T invokeMethod(Class<?> clazz, String methodToExecute, Object... arguments)
由于PowerMock是在Mockito的基础上扩展的框,所以它的很多API与Mockito类似。但又有不少区别。PowerMock能mock成员方法,包括私有的,也能mock静态方法。
PowerMock在进行mock方法时,需要在先使用下面的注解:
@RunWith(PowerMockRunner.class)
//需要在里面声明所有要mock的类
@PrepareForTest(PowerMockSample.class)
public class PowerMockSampleTest {
}
PowerMock提供的mock方法,大致可分为mock(Class<T>
type)、spy(T object)、mockStatic(Class<?>
type, Class<?>
... types)、spy(Class<T>
type)四种方法。前两者用于mock成员方法,后两者则是用于mock静态方法。所以前两者是会返回相应mock实例,而后两者则没有返回值。
在PowerMock里,spy和mock的区别,基本跟Mocktio是类似的。所以,请参考之前关于Mockito的博文,不再赘述。这里,只强调一下:
经由spy(T object)产生的mock实例,通过when...thenReturn...
来mock一个方法。然后用该mock实例调用该方法(或者尝试调用该类的该静态方法)时,在返回指定值之前,会走真实逻辑。但通过doReturn...when...
来mock一个方法,则不会走真实逻辑。
调用spy(Class<T>
type)后,通过when...thenReturn...
来mock一个静态方法。然后在调用该静态方法时,在返回指定值之前,会走真实逻辑。但通过doReturn...when...
来mock一个静态方法,则不会走真实逻辑。
1)mock成员方法
mock(Class<T>
type)、spy(T object)均会生成一个mock实例。只有mock实例,才能调用PowerMock的API来mock成员方法。
@Test
public void spyObject_mockPrivateMethodCalculateThrowException() throws Exception {
int expected = 10;
powerMockSample = PowerMockito.spy(powerMockSample);
//不要使用when(...).thenReturn(...)。会调用你想要mock的方法的真实逻辑。然后才返回mock的结果。
//doReturn方法的注释,也提供了相关解释和例子。
//when(powerMockSample, "privateMethodCalculateThrowException", isA(int.class), isA(int.class)).thenReturn(expected);
doReturn(expected).when(powerMockSample, "privateMethodCalculateThrowException", isA(int.class), isA(int.class));
int actual = Whitebox.invokeMethod(powerMockSample, "privateMethodCalculateThrowException", 1, 2);
assertEquals(expected, actual);
}
@Test
public void mockClass_mockPrivateMethodCalculateThrowException() throws Exception {
int expected = 10;
powerMockSample = PowerMockito.mock(PowerMockSample.class);
//两者均可。均不会走真实逻辑。
//when(powerMockSample,"privateMethodCalculateThrowException", isA(int.class), isA(int.class)).thenReturn(expected);
doReturn(expected).when(powerMockSample, "privateMethodCalculateThrowException", isA(int.class), isA(int.class));
int actual = Whitebox.invokeMethod(powerMockSample, "privateMethodCalculateThrowException", 1, 2);
assertEquals(expected, actual);
}
2)mock静态方法
mockStatic(Class<?>
type, Class<?>
... types)、spy(Class<T>
type)
@Test
public void mockStatic_mockPublicStaticMethodReturnStringButThrowException() throws Exception {
String newValue = "mockPublicStaticMethodReturnStringButThrowException";
PowerMockito.mockStatic(PowerMockSample.class);
//没有抛异常,所以没有走真实逻辑。返回了null。
assertEquals(null, PowerMockSample.publicStaticMethodReturnStringButThrowException());
//when...thenReturn...也是一样的效果。不会走真实逻辑。
//两种when写法没什么区别。
//when(PowerMockSample.class,"publicStaticMethodReturnStringButThrowException").thenReturn(newValue);
//when(PowerMockSample.publicStaticMethodReturnStringButThrowException()).thenReturn(newValue);
//错误的doReturn写法。会抛UnfinishedStubbingException异常。
//doReturn(newValue).when(PowerMockSample.publicStaticMethodReturnStringButThrowException());
doReturn(newValue).when(PowerMockSample.class,"publicStaticMethodReturnStringButThrowException");
assertEquals(newValue, PowerMockSample.publicStaticMethodReturnStringButThrowException());
}
@Test
public void spyClass_mockPublicStaticMethodNoReturnThrowException() throws Exception {
PowerMockito.spy(PowerMockSample.class);
boolean isThrowException = false;
try {
Whitebox.invokeMethod(PowerMockSample.class, "privateStaticMethodNoReturnThrowException");
} catch (Exception e) {
isThrowException = true;
}
//抛异常,执行了真实逻辑。
assertEquals(true,isThrowException);
//传的是mock过的class
//没有返回值的方法,只能通过doNothing...when...
doNothing().when(PowerMockSample.class, "publicStaticMethodNoReturnThrowException");
PowerMockSample.publicStaticMethodNoReturnThrowException();
}
注意:
1.跟Mockito里的spy(Class<T>
type)不同,spy(Class<T>
type)是没有返回值的。它被命名为spyStatic的话,会更恰当一点。因为它跟mockStatic一样,是在mock静态方法时会用到的API。
2.调用doReturn...when...
来mock一个静态方法时,不要通过类名直接调用该方法,如:
//会抛UnfinishedStubbingException异常
doReturn(expected).when(PowerMockSample.publicStaticMethodCalculate(isA(int.class),isA(int.class)));
正确的做法如下:
doReturn(expected).when(PowerMockSample.class, "publicStaticMethodCalculate", isA(int.class), isA(int.class));
反射真的无法修改被final修饰的基本类型和String类型变量?
前面提到PowerMock是通过反射修改对应的值来达到mock的目的。这也就意味着无法对被final修饰的基本类型和String类型变量进行mock。
这点,追踪PowerMock的WhiteBox.setInternalState()方法的源码也可以发现。
private static void checkIfCanSetNewValue(Field fieldToSetNewValueTo) {
int fieldModifiersMask = fieldToSetNewValueTo.getModifiers();
boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
boolean isStaticModifierPresent = (fieldModifiersMask & Modifier.STATIC) == Modifier.STATIC;
if(isFinalModifierPresent && isStaticModifierPresent){
boolean fieldTypeIsPrimitive = fieldToSetNewValueTo.getType().isPrimitive();
if (fieldTypeIsPrimitive) {
throw new IllegalArgumentException("You are trying to set a private static final primitive. Try using an object like Integer instead of int!");
}
boolean fieldTypeIsString = fieldToSetNewValueTo.getType().equals(String.class);
if (fieldTypeIsString) {
throw new IllegalArgumentException("You are trying to set a private static final String. Cannot set such fields!");
}
}
}
但这段代码,也存在一定的问题:
1)相关代码里不涉及修饰符private,抛出的异常信息有误导
2)关键是final修饰的基本类型、String类型变量都无法通过反射修改来达到mock的目的。跟是不是static没啥关系。
3)还有,如果测试类加上注解@RunWith(PowerMockRunner.class)
,该异常无法正常抛出,应该是被try catch了。

但是反射真的无法修改被final修饰的基本类型和String类型变量?
我们先来看下面这段测试:
@Test
public void mockPublicStaticFinalInt() {
//public static final int publicStaticFinalInt = 1;
int newValue = 2;
Whitebox.setInternalState(PowerMockSample.class, "publicStaticFinalInt", newValue);
//注意,这里反射修改成功了
//直接通过反射获取变量的值
assertEquals(newValue, getStaticFieldValue(PowerMockSample.class, "publicStaticFinalInt"));
//注意,这里并不相等。
assertNotEquals(newValue, PowerMockSample.publicStaticFinalInt);
}
为什么变量publicStaticFinalInt的值。明明被修改了,结果却不相等?因为assertNotEquals(newValue, PowerMockSample.publicStaticFinalInt);
这段代码里的PowerMockSample.publicStaticFinalInt
在编译时,在字节码里已经被替换成相应的值。所以,变量publicStaticFinalInt的值再怎么修改。都与它无关。
我们接着看另外一段代码:
int mInt = 1;
String mString = "string";
static int mStaticInt = 1;
static String mStaticString = "staticString";
final int mFinalInt = 1;
final String mFinalString = "finalString";
final static int mFinalStaticInt = 1;
final static String mFinalStaticString = "finalStaticString";
public void test() {
int _int = mInt;
String string = mString;
int staticInt = mStaticInt;
String staticString = mStaticString;
int finalInt = mFinalInt;
String finalString = mFinalString;
int finalStaticInt = mFinalStaticInt;
String finalStaticString = mFinalStaticString;
}
test()方法对应的字节码:
public void test();
Code:
0: aload_0
1: getfield #2 // Field mInt:I
4: istore_1
5: aload_0
6: getfield #4 // Field mString:Ljava/lang/String;
9: astore_2
10: getstatic #8 // Field mStaticInt:I
13: istore_3
14: getstatic #9 // Field mStaticString:Ljava/lang/String;
17: astore 4
19: iconst_1
20: istore 5
22: ldc #6 // String finalString
24: astore 6
26: iconst_1
27: istore 7
29: ldc #11 // String finalStaticString
31: astore 8
33: return
仔细看看test()方法的字节码,非final修饰的变量,比如mInt、mString、mStaticInt、mStaticString,在赋值前,都是通过字节码指令getfield或者getstatic获取对应的实例变量、类变量的值。而final修饰的变量mFinalInt 、mFinalStaticInt在赋值前,通过字节码指令iconst_1加载对应的值1,变量mFinalString、mFinalStaticString通过字节码指令ldc,直接从常量池中加载对应的值,跟对应的实例变量、类变量不相干。
所以,问题的答案应该是:反射其实是能修改某个被final修饰的基本类型或者String类型变量,让它指向新的值。但却无法修改其他使用到该变量的代码里的值。因为在那些代码里,该变量的值在编译期就已经被直接替换成了对应的值,或者指向常量池里的某个常量。
后记
PowerMock弥补了Mockito不能mock私有方法、静态方法、final方法的缺陷,方便我们在不破坏代码封装的情况下,写测试用例。
文中的相关测试例子,以及更多的测试例子均可以在UnitTest里面找到。
更多的测试例子,以及相关API的使用方法,请参考PowerMock源码里的测试用例。