PowerMock官网
编写单元测试仅靠Mockito是不够。因为Mockito无法mock私有方法、final方法及静态方法等。
PowerMock这个framework,主要是为了扩展其他mock框架,如Mockito、EasyMock。它使用一个自定义的类加载器,纂改字节码,突破Mockito无法mock静态方法、构造方法、final类、final方法以及私有方法的限制。
不过,听起来很牛逼。但很多时候你也可以不用它。有一种简单的方法,可以替代它,比如,你可以提高那些成员变量、成员方法的可见性,然后,用 @VisibleForTesting
标注该变量或方法。对,就这么简单。android源码也是这么干的。你可以在源码里看见不少这样的注解。
不过如果你要mock的是一些静态方法或者第三方库的私有方法,那只能含泪使用PowerMock了。
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的版本相对应。
参考资料: Using PowerMock with Mockito 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(注意:不包括基本类型的包装类型)。至于为什么,文末也会对这个问题进行探讨。
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)); 复制代码
前面提到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类型变量,让它指向新的值。但却无法修改其他使用到该变量的代码里的值。因为在那些代码里,该变量的值在编译期就已经被直接替换成了对应的值,或者指向常量池里的某个常量。