PowerMock官网java
编写单元测试仅靠Mockito是不够。由于Mockito没法mock私有方法、final方法及静态方法等。android
PowerMock这个framework,主要是为了扩展其余mock框架,如Mockito、EasyMock。它使用一个自定义的类加载器,纂改字节码,突破Mockito没法mock静态方法、构造方法、final类、final方法以及私有方法的限制。git
不过,听起来很牛逼。但不少时候你也能够不用它。有一种简单的方法,能够替代它,好比,你能够提升那些成员变量、成员方法的可见性,而后,用@VisibleForTesting
标注该变量或方法。对,就这么简单。android源码也是这么干的。你能够在源码里看见很多这样的注解。github
不过若是你要mock的是一些静态方法或者第三方库的私有方法,那只能含泪使用PowerMock了。api
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的版本相对应。bash
PowerMock能够mock变量。并非什么黑科技,其实就是经过Java反射修改变量。PowerMock只是对Java反射进行了封装,提供相应的API,方便咱们使用。相应的方法,就在WhiteBox
这个类里,下面列出两个常用的方法:app
//反射修改某个变量的值。修改静态变量的值时,第一个参数传相应的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类型变量,让它指向新的值。但却没法修改其余使用到该变量的代码里的值。由于在那些代码里,该变量的值在编译期就已经被直接替换成了对应的值,或者指向常量池里的某个常量。
PowerMock弥补了Mockito不能mock私有方法、静态方法、final方法的缺陷,方便咱们在不破坏代码封装的状况下,写测试用例。
文中的相关测试例子,以及更多的测试例子都可以在UnitTest里面找到。
更多的测试例子,以及相关API的使用方法,请参考PowerMock源码里的测试用例。