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
參考資料: Using PowerMock with MockitoPowerMock能夠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源碼裏的測試用例。