在博客Android單元測試之Mockito中贴膘,主要介紹了Mockito測試框架和基本使用。在博客結(jié)束時(shí)抬虽,我們提出了一個(gè)問題官觅,由于Mockito的局限性,對final阐污、private休涤、static等方法不能mock,那如何對這樣的方法進(jìn)行單元測試呢笛辟?我們是不是真的束手無策呢功氨?肯定不是的啦,今天我們一起來學(xué)習(xí)PowerMockito測試框架手幢,又是如何完美的彌補(bǔ)Mockito測試框架的不足呢捷凄。
PowerMockito簡介
PowerMock是一個(gè)擴(kuò)展了其它如EasyMock等mock框架的、功能更加強(qiáng)大的框架围来。PowerMock使用一個(gè)自定義類加載器和字節(jié)碼操作來模擬靜態(tài)方法跺涤,構(gòu)造函數(shù),final類和方法监透,私有方法桶错,去除靜態(tài)初始化器等等。通過使用自定義的類加載器才漆,簡化采用的IDE或持續(xù)集成服務(wù)器不需要做任何改變。熟悉PowerMock支持的mock框架的開發(fā)人員會(huì)發(fā)現(xiàn)PowerMock很容易使用佛点,因?yàn)閷τ陟o態(tài)方法和構(gòu)造器來說醇滥,整個(gè)的期望API是一樣的黎比。PowerMock旨在用少量的方法和注解擴(kuò)展現(xiàn)有的API來實(shí)現(xiàn)額外的功能。目前PowerMock支持EasyMock和Mockito鸳玩。
PowerMockito入門
Gradle配置如下:
repositories {
jcenter()
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile "junit:junit:4.12"
testCompile "org.assertj:assertj-core:1.7.0"
testCompile "org.robolectric:robolectric:3.3.2"
// PowerMock brings in the mockito dependency
testCompile 'org.powermock:powermock-module-junit4:1.6.5'
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.5'
testCompile 'org.powermock:powermock-api-mockito:1.6.5'
testCompile 'org.powermock:powermock-classloading-xstream:1.6.5'
}
PowerMock有三個(gè)重要的注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest({YourClassWithEgStaticMethod.class})
@PowerMockIgnore("javax.management.*")
如果你的測試用例里沒有使用注解@PrepareForTest阅虫,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然不跟。當(dāng)你需要使用PowerMock強(qiáng)大功能(Mock靜態(tài)颓帝、final、私有方法等)的時(shí)候窝革,就需要加注解@PrepareForTest购城。這一點(diǎn)和Mockito的使用方式是類似的,要么使用這種注解的方式
@RunWith(PowerMockRunner.class)
@PrepareForTest({YourClassWithEgStaticMethod.class})
要么使用注解加代碼的方式
@PrepareForTest({YourClassWithEgStaticMethod.class})
MockitoAnnotations.initMocks(this);
其中@PrepareForTest注解是聲明需要進(jìn)行mock的靜態(tài)類虐译,如果你需要聲明多個(gè)靜態(tài)類瘪板,使用
@PrepareForTest({Example1.class, Example2.class, ...})
這種方式聲明。
最后就是@PowerMockIgnore注解漆诽,聲明package路徑侮攀,表示不使用PowerMockito來加載所聲明的package路徑的類。
PowerMockito使用
(1) 普通Mock:Mock參數(shù)傳遞的對象
測試目標(biāo)代碼:
public class CommonExample {
public boolean callArgumentInstance(File file) {
return file.exists();
}
}
測試用例代碼:
public class CommonExampleTest {
@Test
public void testCallArgumentInstance() {
File file = PowerMockito.mock(File.class);
CommonExample commonExample = new CommonExample();
PowerMockito.when(file.exists()).thenReturn(true);
Assert.assertTrue(commonExample.callArgumentInstance(file));
}
}
注意到了么有厢拭,普通Mock不需要加@RunWith和@PrepareForTest注解兰英,在這種情況下,使用Mockito也是可以實(shí)現(xiàn)的供鸠。上述步驟如下:
- 通過
PowerMockito.mock(File.class)
創(chuàng)建出一個(gè)mock對象 - 然后再通過
PowerMockito.when(file.exists()).thenReturn(true);
來指定這個(gè)mock對象具體的行為 - 再將mock對象作為參數(shù)傳遞個(gè)測試方法畦贸,執(zhí)行測試方法。
(2) Mock方法內(nèi)部new出來的對象
測試目標(biāo)代碼:
public class CommonExample {
public boolean callArgumentInstance(String path) {
File file = new File(path);
return file.exists();
}
}
測試用例代碼:
public class CommonExampleTest extends BasePowerMockTestCase {
@Test
@PrepareForTest(CommonExample.class)
public void callCallArgumentInstance2() throws Exception {
File file = PowerMockito.mock(File.class);
CommonExample commonExample = new CommonExample();
PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);
PowerMockito.when(file.exists()).thenReturn(true);
Assert.assertTrue(commonExample.callArgumentInstance("aaa"));
File newFile = Mockito.mock(File.class);
newFile.exists();
Mockito.verify(newFile).exists();
}
}
當(dāng)使用PowerMockito.whenNew().thenReturn()
方法時(shí)回季,必須加上注解@PrepareForTest和@RunWith家制,注解@PrepareForTest里寫的類是需要mock的new對象代碼所在的類。需要mock的對象是在方法內(nèi)部new出來的泡一,這是一種比較常見的mock方式颤殴。 步驟(已經(jīng)講過的步驟省略):
- 通過
PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);
來指定當(dāng)以參數(shù)為"aaa"創(chuàng)建File對象的時(shí)候,返回已經(jīng)mock的File對象鼻忠。 - 在測試方法之上加注解
@PrepareForTest(CommonExample.class)
涵但,注解里寫的類是需要mock的new對象代碼所在的類。
(3) Mock普通對象的final方法
測試目標(biāo)代碼:
public class ClassDependency {
public final boolean isAlive() {
// do something
return false;
}
}
public class CommonExample {
public boolean callFinalMethod(ClassDependency dependency) {
return dependency.isAlive();
}
}
測試用例代碼:
public class CommonExampleTest extends BasePowerMockTestCase {
@Test
@PrepareForTest(ClassDependency.class)
public void callFinalMethod() throws Exception {
ClassDependency depencency = PowerMockito.mock(ClassDependency.class);
CommonExample commonExample = new CommonExample();
PowerMockito.when(depencency.isAlive()).thenReturn(true);
Assert.assertTrue(commonExample.callFinalMethod(depencency));
}
}
Mock的步驟和之前的一樣帖蔓,只是需要在測試方法之上加注解@PrepareForTest(ClassDependency.class)矮瘟,注解里寫的類是需要mock的final方法所在的類。
(4) Mock普通類的靜態(tài)方法
測試目標(biāo)代碼:
public final class Utils {
public static String generateNewUUId() {
return UUID.randomUUID().toString();
}
}
測試用例代碼:
@PrepareForTest(Utils.class)
public class PowerMockSampleTest extends BasePowerMockTestCase {
@Test
public void testPrintUUID() throws Exception {
PowerMockito.mockStatic(Utils.class);
PowerMockito.when(Utils.generateNewUUId()).thenReturn("FAKE UUID");
PowerMockSample sample = new PowerMockSample();
assertThat(sample.printUUID()).isEqualTo("FAKE UUID");
}
}
當(dāng)需要mock靜態(tài)方法的時(shí)候塑娇,必須加注解@PrepareForTest和@RunWith澈侠。注解@PrepareForTest里寫的類是靜態(tài)方法所在的類。
(5) Mock 私有方法
測試目標(biāo)方法:
public class CommonExample {
public boolean callPrivateMethod() {
return isExist();
}
private boolean isExist() {
return false;
}
}
測試用例代碼:
public class CommonExampleTest extends BasePowerMockTestCase {
@Test
@PrepareForTest(CommonExample.class)
public void testCallPrivateMethod() throws Exception {
CommonExample commonExample = PowerMockito.mock(CommonExample.class);
PowerMockito.when(commonExample.callPrivateMethod()).thenCallRealMethod();
PowerMockito.when(commonExample, "isExist").thenReturn(true);
Assert.assertTrue(commonExample.callPrivateMethod());
}
}
和Mock普通方法一樣埋酬,只是需要加注解@PrepareForTest(CommonExample.class)哨啃,注解里寫的類是私有方法所在的類烧栋。
(6) Mock系統(tǒng)類的靜態(tài)和final方法
目標(biāo)測試方法:
public class CommonExample {
public String callSystemStaticMethod(String str) {
return System.getProperty(str);
}
}
測試用例代碼:
public class CommonExampleTest extends BasePowerMockTestCase {
@Test
@PrepareForTest(CommonExample.class)
public void callSystemStaticMethod() {
CommonExample commonExample = new CommonExample();
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");
Assert.assertEquals("bbb", commonExample.callSystemStaticMethod("aaa"));
}
}
和Mock普通對象的靜態(tài)方法、final方法一樣拳球,只不過注解@PrepareForTest里寫的類不一樣 审姓,注解里寫的類是需要調(diào)用系統(tǒng)方法所在的類。
(7) Mock普通類的私有變量
測試目標(biāo)方法:
public class PowerMockSample {
private static final int STATE_NOT_READY = 0;
private static final int STATE_READY = 1;
private int mState = STATE_NOT_READY;
public boolean doSomethingIfStateReady() {
if (mState == STATE_READY) {
// DO some thing
return true;
} else {
return false;
}
}
}
測試用例代碼:
public class PowerMockSampleTest extends BasePowerMockTestCase {
@Test
public void testDoSomethingIfStateReady() throws Exception {
PowerMockSample sample = new PowerMockSample();
Whitebox.setInternalState(sample, "mState", 1);
assertThat(sample.doSomethingIfStateReady()).isTrue();
}
}
當(dāng)需要mock私有變量mState的時(shí)候祝峻,不需要加注解@PrepareForTest和@RunWith魔吐,而是使用Whitebox來mock私有變量mState并注入你預(yù)設(shè)的變量值。
上面介紹了PowerMockito的簡單使用莱找,對static方法酬姆、構(gòu)造方法、private方法以及final方法的mock支持宋距,專治Mockito各種不服轴踱。不過還有其他的API沒有介紹,感興趣的同學(xué)可以參考PowerMockito官方wiki谚赎。
PowerMockito原理簡單介紹
我們看一下PowerMockito的依賴:
可以看出來淫僻,它有兩個(gè)重要的依賴:javassist和objenesis,javassist是一個(gè)修改java字節(jié)碼的工具包壶唤,objenesis是一個(gè)繞過構(gòu)造方法來實(shí)例化一個(gè)對象的工具包雳灵。由此看來,PowerMock的本質(zhì)是通過修改字節(jié)碼來實(shí)現(xiàn)對靜態(tài)和final等方法的mock的闸盔。
我們結(jié)合上面的例子來介紹:
testCallPrivateMethod()被注解@PrepareForTest(CommonExample.class)標(biāo)注以后悯辙,在運(yùn)行時(shí),會(huì)創(chuàng)建一個(gè)org.powermock.core.classloader.MockClassLoader對象來加載該測試用例使用到的類(系統(tǒng)類除外)迎吵。
PowerMockito會(huì)根據(jù)你的mock要求躲撰,去修改寫在注解@PrepareForTest里的class文件(當(dāng)前測試類會(huì)自動(dòng)加入注解中),以滿足特殊的mock需求击费。例如:去除final方法的final標(biāo)識(shí)拢蛋,在靜態(tài)方法的最前面加入自己的虛擬實(shí)現(xiàn)等。
如果mock的是系統(tǒng)類的final方法和靜態(tài)方法蔫巩,PowerMockito不會(huì)直接修改系統(tǒng)類的class文件谆棱,而是修改調(diào)用系統(tǒng)類的class文件,以滿足mock需求
PowerMockito踩坑
1.使用PowerMockito會(huì)提示classloader錯(cuò)誤
加入注解:@PowerMockIgnore(YourPackagePath.*)來解決由于MockClassLoader造成的class加載錯(cuò)誤圆仔,通過這個(gè)注解可以讓報(bào)錯(cuò)的類使用系統(tǒng)的ClassLoader來加載報(bào)錯(cuò)類垃瞧。如下:
setUp now
java.security.NoSuchAlgorithmException: class configured for SSLContext: sun.security.ssl.SSLContextImpl$TLSContext not a SSLContext
at sun.security.jca.GetInstance.checkSuperClass(GetInstance.java:260)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:237)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
at javax.net.ssl.SSLContext.getInstance(SSLContext.java:156)
at me.ele.shopcenter.network.request.CustomClient.getSslSocketFactory(CustomClient.java:124)
這種就是ClassLoader造成的SSLContext對象不一致的問題,只要加載注解@PowerMockIgnore({"sun.security.*", "javax.net.*"})
就可以解決坪郭。
2.PowerMockito 和Mockito mock出來的對象不能相互使用个从,否則會(huì)拋出異常
小結(jié)
小伙伴們,感受到PowerMockito的強(qiáng)大了吧歪沃。根據(jù)實(shí)踐發(fā)現(xiàn)嗦锐,推薦使用JUnit4+Mockito+PowerMockito測試工具鸵隧,在Java單元測試中可以說是無所不能。
感謝您對本篇博客的關(guān)注意推,要是有什么不足歡迎指正!前面介紹了在JVM運(yùn)行環(huán)境下的單元測試珊蟀,要是在Android呢菊值?PowerMockito無法解決,因?yàn)镻owerMockito是運(yùn)行在JVM上育灸,無法調(diào)用Android相關(guān)的類和方法腻窒。仔細(xì)的同學(xué),你會(huì)發(fā)現(xiàn)磅崭,在上面的Gradle配置中有這么一句testCompile "org.robolectric:robolectric:3.3.2"
儿子,在前面的博客中都沒有講到robolectric。其實(shí)robolectric就是解決在JVM中可以調(diào)用Android相關(guān)的類和方法進(jìn)行單元測試的砸喻,具體情況柔逼,請關(guān)注博客Android單元測試之Robolectric。