一、介紹
PowerMockito 可以用來 Mock 掉 final 方法(變量)、靜態方法(變量)、私有方法(變量)。想要使用 PowerMockito Mock掉這些內容,需要在編寫的測試類上使用 注解:@RunWith(PowerMockRunner.class) 及 @PrepareForTest({First.class,Second.class}),對於注解 @PrepareForTest 網上有介紹說,可以不用注解到測試類上,直接注解到測試方法上,但是我在使用的時候不知道為什么不行,僅能注解到測試方法上。
二、調用別的類中的靜態方法
(1) Mock 掉靜態方法
Mock 掉靜態方法,需要在測試類上使用 @RunWith 及 @PrepareForTest 並 使用PowerMockito.mockStatic方法。
例子:

import java.io.File; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; public class FileUtil extends FileUtils{ private static final String FILE_EXTEND_SPLIT = "."; /***文件分割符****/ public static final String FILE_SEPARATOR = "/"; /** * 獲取文件名(沒有擴展內容) * @param fileName 文件名稱 * @return */ public static String getNameNoExtend(String fileName){ if(StringUtils.isNotBlank(fileName)){ String name = fileName; int fileExtendSplitIndex = name.lastIndexOf(FILE_EXTEND_SPLIT); if(-1 != fileExtendSplitIndex && fileExtendSplitIndex != name.length()-1){ name = name.substring(0, fileExtendSplitIndex); } return name; } return StringUtils.EMPTY; } /** * 判斷文件是否是目錄 * @param file * @return */ public static boolean isDirectory(File file){ if(isExist(file) && file.isDirectory()){ return true; } return false; } /** * 判斷文件是否存在 * @param file * @return */ public static boolean isExist(File file){ if(null != file && file.exists()){ return true; } return false; } }

import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import org.apache.commons.lang3.StringUtils; public class FileExecute { public InputStream getFileInputStream(String filePath){ if(StringUtils.isNotBlank(filePath)){ File file = new File(filePath); if(FileUtil.isExist(file)){ try { return new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } } return null; } }
import java.io.File; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import com.powerMockito.util.TestRunner; @PrepareForTest({FileUtil.class}) public class FileExecuteTest extends TestRunner{ private FileExecute fileExecute = new FileExecute(); @Test public void testGetFileInputStream() { String filePath = PathUtil.getClassPath() + "FileExecute.properties"; PowerMockito.mockStatic(FileUtil.class); PowerMockito.when(FileUtil.isExist(Mockito.any(File.class))).thenReturn(false); Assert.assertNull(fileExecute.getFileInputStream(filePath));
PowerMockito.verifyStatic(); //可以使用verifyStatic方法來校驗靜態Mock是否執行,該方法表示校驗靜態Mock是否執行一次 } }
(2) 設置類中的靜態共有final變量
在類(A類)中定義了公有靜態final常量,類(B類)中會用到A類的靜態常量,在測試B中該方法,可能希望改變這個靜態常量的值來進行正常測試(如果測試方法使用靜態常量原來值無法進行測試,但程序正常執行時用靜態常量原來值是可以執行的且是正確的)。在測試方法中的做法是可以先生成A類對象,通過WhiteBox來更改該常量值。當然測試類上仍然需要有 @RunWith 及 @PrepareForTest。
三、靜態類公共方法調用其自身私有方法

import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import org.apache.commons.lang3.StringUtils; /** * <p>靜態類</p> * @version V1.0 */ public class StaticFileExecute { /***常量***/ private static final String HELLO = new String("hello"); /***非常量靜態私有方法****/ private static String NO_HELLO = "noHello"; private static InputStream getFileInputStream(String filePath){ if(StringUtils.isNotBlank(filePath)){ File file = new File(filePath); if(FileUtil.isExist(file)){ try { return new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } } return null; } public static String getStaticFinalHello(){ return HELLO; } public static String getFinalNoHello(){ return NO_HELLO; } public static boolean hasInputStream(String filePath){ InputStream inputstream = getFileInputStream(filePath); if(null != inputstream){ return true; } return false; } }
mport static org.junit.Assert.*; import java.io.File; import java.io.FileInputStream; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; /** * <p>針對靜態類中的方法進行測試</p> * @version V1.0 */ @RunWith(PowerMockRunner.class) @PrepareForTest({FileUtil.class,StaticFileExecute.class}) public class StaticFileExecuteTest { private String filePath = PathUtil.getClassPath() + "FileExecute.properties"; /** * 公共靜態方法調用私有靜態方法,對私有靜態方法進行mock * @throws Exception */ @Test public void testHasInputStream() throws Exception { PowerMockito.spy(StaticFileExecute.class); PowerMockito.doReturn(null).when(StaticFileExecute.class,"getFileInputStream",filePath); Assert.assertFalse(StaticFileExecute.hasInputStream(filePath)); /***靜態方法被執行了一次***/ PowerMockito.verifyPrivate(StaticFileExecute.class,Mockito.times(1)).invoke("getFileInputStream",filePath); PowerMockito.doReturn(new FileInputStream(new File(filePath))).when(StaticFileExecute.class,"getFileInputStream",filePath); Assert.assertTrue(StaticFileExecute.hasInputStream(filePath)); /***靜態方法被執行了二次***/ PowerMockito.verifyPrivate(StaticFileExecute.class,Mockito.times(2)).invoke("getFileInputStream",filePath); } @Test public void testGetStringsAfterSplit() { fail("Not yet implemented"); } }
在示例中,靜態類 StaticFileExecute 中的 公共靜態 hasInputStream 方法使用私有方法 getFileInputStream。對 hasInputStream 進行測試的時候,需要將 getFileInputStream 方法 mock 掉。mock步驟:
- 對 StaticFileExecute 靜態類型進行spy mock;(spy mock 出的表示對象除非指明when,才會返回指定的內容,否則真實執行);
- 對靜態類中的私有方法進行mock;(使用doReturn,這樣程序不會進入私有方法內部);
- 校驗私有方法被執行的次數,使用 verifyPrivate;
上述測試用例在Junit4的環境下可以成功執行,但是在統計覆蓋率的時候,@preparefortest 中的類 覆蓋率為0;為了能夠統計出覆蓋率,需要進行修正。
(1) eclipse 使用 插件;
- 使用 EclEmma Java Code Coverage for Eclipse 插件;
- 修改測試類中內容,使用JavaAgent 及 rule;
import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import org.junit.Assert; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.agent.PowerMockAgent; import org.powermock.modules.junit4.rule.PowerMockRule; /** * <p>針對靜態類中的方法進行測試</p> * @version V1.0 */ @PrepareForTest({FileUtil.class,StaticFileExecute.class}) public class JavaAgentStaticExecuteTest { private String filePath = PathUtil.getClassPath() + "FileExecute.properties"; @Rule public PowerMockRule rule = new PowerMockRule(); static { PowerMockAgent.initializeIfNeeded(); } /** * 公共靜態方法調用私有靜態方法,對私有靜態方法進行mock * @throws Exception */ @Test public void testHasInputStream() throws Exception { PowerMockito.spy(StaticFileExecute.class); PowerMockito.doReturn(null).when(StaticFileExecute.class,"getFileInputStream",filePath); Assert.assertFalse(StaticFileExecute.hasInputStream(filePath)); /***靜態方法被執行了一次***/ PowerMockito.verifyPrivate(StaticFileExecute.class,Mockito.times(1)).invoke("getFileInputStream",filePath); PowerMockito.doReturn(new FileInputStream(new File(filePath))).when(StaticFileExecute.class,"getFileInputStream",filePath); Assert.assertTrue(StaticFileExecute.hasInputStream(filePath)); /***靜態方法被執行了一次***/ PowerMockito.verifyPrivate(StaticFileExecute.class,Mockito.times(2)).invoke("getFileInputStream",filePath); } @Ignore public void testGetStringsAfterSplit() { fail("Not yet implemented"); } }
- 在執行的時候,需要配置JVM參數: -ea -noverify -javaagent:D:/XXXX/.m2/repository/org/powermock/powermock-module-javaagent/1.7.0/powermock-module-javaagent-1.7.0.jar
POM 文件配置:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.powerMockito.example</groupId> <artifactId>PowerMockitoExample</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <junit.version>4.12</junit.version> <mockito-all.version>1.10.19</mockito-all.version> <powermock.version>1.7.0</powermock.version> <hamcrest-library.version>1.3</hamcrest-library.version> <commons-collections.version>3.2.2</commons-collections.version> <commons-lang3.version>3.1</commons-lang3.version> <commons-io.version>1.4</commons-io.version> <slf4j-log4j12.version>1.6.2</slf4j-log4j12.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-log4j12.version}</version> </dependency> <!-- powermock --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>${mockito-all.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4-rule-agent</artifactId> <version>1.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-classloading-xstream</artifactId> <version>1.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>${hamcrest-library.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>${commons-collections.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons-io.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!-- findbugs --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <configuration> <threshold>High</threshold> <effort>Default</effort> <findbugsXmlOutput>true</findbugsXmlOutput> <findbugsXmlOutputDirectory>target/site/findbugs</findbugsXmlOutputDirectory> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.20</version> <configuration> <argLine>-Xms256m -Xmx256m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m -Dfile.encoding=utf-8 -javaagent:${settings.localRepository}\org\powermock\powermock-module-javaagent\1.7.0\powermock-module-javaagent-1.7.0.jar -ea -noverify</argLine> <useSystemClassloader>true</useSystemClassloader> <includes> <include>**/*Test.java</include> </includes> </configuration> </plugin> <!-- cobertura --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <configuration> <format>xml</format> <encoding>utf-8</encoding> <instrumentation> <includes> <include>**/*.class</include> </includes> </instrumentation> </configuration> </plugin> </plugins> </build> </project>
(2) Maven跑單元測試
如果通過 Maven 命令跑通單元測試,並生成測試報告與覆蓋率報告。需要如上圖的 POM文件的配置。但需注意的是:目前執行命令 clean package cobertura:cobertura 直接統計覆蓋率會報錯,還沒有找到解決方案。
補充說明
使用該工具進行Mock的時候,需要注意PowerMock有兩個重要的注解:
–@RunWith(PowerMockRunner.class)
–@PrepareForTest( { YourClassWithEgStaticMethod.class })
如果單元測試用例里沒有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。當需要使用PowerMock強大功能(Mock靜態、final、私有方法等)的時候,就需要加注解@PrepareForTest。
(1) 某個實例對象或靜態類部分方法需要mock,其它方法正常執行
該種情況主要發生在一個單元測試用例中,在該測試用例中,設計的邏輯是對某個實例對象或靜態類的一個方法進行mock,使其返回自己想要的結果,而去別的方法在該用例中正常執行。此時可以使用PowerMockito.spy()。示例如下:
@Before public void before() throws Exception{ observable = PowerMockito.spy(InstallProgressObservable.getDefaultInstance()); //spy的對象類要放入到@prepareForTest中 PowerMockito.doNothing().when(observable, "notifyObs"); observable.setDetail(""); } |
說明:
在上面的代碼中mock了InstallProgressObservable對象,而只有執行該對象的notifyObs方法時,才不執行任何動作。其它方法均真實執行。spy的對象類要放入到@prepareForTest中。這里把模擬結果(PowerMockito.doNothing)放在when前面,表示不會真是執行后面的方法,否則程序中會真是執行到,則不會達到模擬的效果。
(2) Mock靜態類的公有靜態方法
對靜態類的靜態方法進行mock,指定其返回模擬數據,此時可以使用PowerMockito.mockStatic方法,並使用PowerMockito.verifyStatic進行校驗,主要是校驗靜態方法是否被執行到。
示例如下:
@Test public void testStartTool() throws Exception { PowerMockito.mockStatic(CmdUtil.class); PowerMockito.doReturn(false).when(CmdUtil.class,"cmdBool",Mockito.anyString(), Mockito.anyString(), Mockito.anyVararg()); //這種寫法僅在mock的對象填寫僅@PrepareForTest中才可以 Whitebox.invokeMethod(ToolInstall.class, "startTool", toolInfo,observable); PowerMockito.verifyStatic(Mockito.times(1)); CmdUtil.cmdBool(Mockito.anyString(), Mockito.anyString(), Mockito.anyVararg()); } |
說明:
在上面的代碼中mock了CmdUtil.cmdBool方法,首先使用PowerMockito.mockStatic模擬靜態類(該靜態類需要登記到 @PrepareForTest中);其次是進行對該靜態類中的某個方法進行mock數據;最后在執行完成之后,進行靜態類的校驗,判斷mock的靜態方法是否被執行,使用PowerMockito.verifyStatic,注意:在這行后面需要將mock的靜態方法再執行一遍。
(3) 執行私有方法
在寫單元測試用例的時候,可以直接針對私有方法寫單元測試用例,此時面臨調用私有方法的情況,通常情況下,我們會寫反射類來執行我們測試的私有方法,但是當需要校驗參數,讓參數為null的時候,直接使用反射類是不行的,需要在反射類的基礎上做些額外的邏輯。面對該問題,PowerMockito提供了調用私有方法的方式,直接使用Whitebox.invokeMethod。
示例如下:
@Test public void testStartTool() throws Exception { Whitebox.invokeMethod(ToolInstall.class, "startTool", toolInfo,observable); } |
說明:
在上面的代碼中直接使用Whitebox.invokeMethod即可,如果是要對私有方法進行null參數校驗,參數要指明是什么類型,如上面的代碼改為:Whitebox.invokeMethod(ToolInstall.class,
"startTool", (ToolInfo)null,(InstallProgressObservable)null);
(4) Mock靜態類私有方法
桌面程序中經常出現直接調用靜態類的方法,而該方法又會調用該類的其它私有靜態方法。寫單元測試時,有時需要將靜態私有方法進行mock,模擬各種數據校驗測試的靜態方法的邏輯性,此時就需要對靜態私有方法進行Mock,也就是該類的一部分方法要正常執行,另一部分要被mock掉。面對該中情況下,使用PowerMockito.spy,校驗私有方法的執行情況,使用PowerMockito.verifyPrivate。
示例如下:
@Test public void testInstallTool_product_notool() throws Exception { PowerMockito.spy(ToolInstall.class); PowerMockito.doReturn(null).when(ToolInstall.class,"getInstallableTool",productName,rootInstallPath); ToolInstall.installTool(productName, rootInstallPath, observable); PowerMockito.verifyPrivate(ToolInstall.class, Mockito.times(1)).invoke("getInstallableTool", productName,rootInstallPath); PowerMockito.doReturn(Collections.emptyMap()).when(ToolInstall.class,"getInstallableTool",productName,rootInstallPath); ToolInstall.installTool(productName, rootInstallPath, observable); PowerMockito.verifyPrivate(ToolInstall.class, Mockito.times(2)).invoke("getInstallableTool", productName,rootInstallPath); } |
說明:
在上面的代碼中Mock了ToolInstall 靜態類中的getInstallableTool 靜態私有方法,而該類的公有installTool靜態方法調用getInstallableTool私有方法,測試代碼的意圖是想讓該私有方法返回mock的數據。直接使用PowerMockito.spy mock靜態類(該ToolInstall靜態類需要登記到 @PrepareForTest中),使用PowerMockito.doReturn….when…..的方式mock私有方法,校驗時使用PowerMockito.verifyPrivate,第一個參數為靜態類,第二個參數為執行次數。在使用PowerMockito.verifyPrivate時,必須在其返回對象直接使用invoke方法,否則該校驗不起作用;invoke中要指明mock的私有方法名稱及私有方法需要的參數,該參數應該與mock該方法時使用的一致。
(5) Mock構造函數
在一個測試方法中若需對被測方法中的某個對象生成進行模擬,此時需要對其構造函數進行mock,直接生成測試方法中指定的對象。可以使用PowerMockito.whenNew。
示例如下:
@Test public void testGetInstallableTool_no_tool_directory() throws Exception{ File toolFile = new File("D:\\hello"); PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(toolFile); Map<String,ToolInfo> toolInfoMap = (Map<String, ToolInfo>)TestReflectUtil.callPrivateMethod(ToolInstall.class, "getInstallableTool", this.productName,this.rootInstallPath); Assert.assertTrue(CollectionUtil.isEmpty(toolInfoMap)); } |
說明:
在上面的代碼中對ToolInstall靜態類的getInstallableTool方法進行了測試,getInstallableTool方法中要生成File對象,此用例對生成的File對象進行Mock(該ToolInstall靜態類需要登記到 @PrepareForTest中),指向指定的文件夾。代碼中使用了PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(toolFile); 這句進行了mock,模擬返回的對象為測試方法中創建的File對象。
(6) 阻止靜態代碼塊執行
某個靜態類中有靜態代碼塊。由於每次執行一個Test類,都會重新加載該靜態類,每加載一次靜態類,靜態代碼塊均會被執行。有時不希望執行靜態代碼,此時可以在測試類中使用注解 @SuppressStaticInitializationFor。
示例如下:
@SuppressStaticInitializationFor("com.hikvision.discinstall.language.core.TranslatorFactory") //阻止靜態代碼塊運行 public class TranslatorFactoryTest{ @Rule public PowerMockRule rule = new PowerMockRule(); static { PowerMockAgent.initializeIfNeeded(); } } |
說明:
如果多個靜態類都在該測試類中被執行到,且這些類都有靜態代碼塊,該注解中的值使用數組的形式。使用該方式有一點不好的地方就是登記到該注解的靜態類類,其中的被賦予初始值的靜態屬性均不生效,值均為null。為了讓靜態屬性賦值仍然生效,此時需要使用PowerMockRule和Agent。要引入powermock-module-junit4-rule-agent.jar包,且測試類中不能使用@RunWith(PowerMockRunner.class),具體可見上述代碼中test類中的代碼,此時必須有該段代碼,但有該段又引入另一個問題,此時無法進行bug調試,打斷點不生效,因此建議在調試測試用例時,可以不先禁止靜態代碼塊的執行,當沒有問題時,再禁止即可。
(7) 覆蓋率統計
被測試類在測試方法中被mock掉,且登記到了@PrepareForTest注解中時,此時進行代碼統計,被測試類不會被統計到。此時需要使用PowerMockRule。具體用法見(6)中的代碼說明部分。
(8) Mock jdk提供的靜態方法
當被測試方法中調用了jdk的靜態方法,且想在測試方法中將其mock掉,除了安裝mock靜態類的公有方法方式來之外,還需要將調用jdk靜態方法所屬類登記到測試類中的@PrepareTest中。
示例如下:
@PrepareForTest({LanguageUtil.class,Locale.class}) public class LanguageUtilTest{ @Rule public PowerMockRule rule = new PowerMockRule(); static { PowerMockAgent.initializeIfNeeded(); } @Test public void testGetSystemLanguage_fail() throws Exception { //系統提供的靜態方法,需要將調用靜態方法的類放入到prepareForTest PowerMockito.mockStatic(Locale.class); PowerMockito.doReturn(null).when(Locale.class,"getDefault"); Assert.assertTrue(StringUtils.isBlank(LanguageUtil.getSystemLanguage())); } @Test public void testUUID() throws Exception{ PowerMockito.mockStatic(UUID.class); PowerMockito.doReturn(null).when(UUID.class,"randomUUID"); Assert.assertFalse(LanguageUtil.testUUID()); } } |
說明:
在該實例中要測試LanguageUtil.getSystemLanguage()方法,該方法在實現時調用了jdk的Locale.getDefault()方法,此時要對它進行Mock,使用PowerMockito.mockStatic方式,同時需要將Local類及LanguageUtil類登記到@ PrepareForTest中。
(9) 對常量進行設置
在寫單元測試用例時,也經常會對某個靜態類中的某個常量進行模擬值,此時可以使用Whitebox.setInternalState。
示例如下:
@PrepareForTest({PathConstants.class}) public class ConfigResolverNoPrepareTest extends TestRunner{ @Test public void generateViewConfig_main() throws Exception{ Whitebox.setInternalState(PathConstants.class, "PRODUCT_PARENT_PATH",PathConstants.CLASS_PATH + "platform"); //私有靜態 } |
說明:
使用Whitebox.setInternalState可以對任意的對象屬性或類屬性進行設置模擬值。但是需要注意的是,若是對常量設置值,需要將類登記到測試類的@PrepareForTest中。