PowerMock詳解

- 概述

PowerMock有兩個重要的註解:
@RunWith(PowerMockRunner.class)
@prepareForTest({MyObect.class})
@PrepareForTest註解和@RunWith註解是結合使用的,不要單獨使用它們中的任何一個,否則不起作用。當使用
PowerMock去mock靜態,final或者私有方法時,需要加上這兩個註解。

注意,在你輸入@RunWith註解時,Eclipse會自動導入org.powermock.modules.junit4.legacy.PowerMockRunner包,記得把它換成org.powermock.modules.junit4.PowerMockRunner,否則會拋java.lang.NoClassDefFoundError.

- 普通Mock(1)

測試目標代碼:
public boolean callArgumentInstance(File file) {
     return file.exists();
}
測試用例代碼: 
@Test 
public void testCallArgumentInstance() {
    File file = PowerMockito.mock(File.class); 
    ClassUnderTest underTest = new ClassUnderTest();
    PowerMockito.when(file.exists()).thenReturn(true);
    Assert.assertTrue(underTest.callArgumentInstance(file)); 
}
說明:普通Mock不需要加@RunWith和@PrepareForTest註解。

- 普通Mock(2)

測試目標代碼:

public String getFilePath() {
    return path;
}
public String getPayloadName() {
    String pathWithName = getFilePath();
    try {
        int index = pathWithName.lastIndexOf(Constant.SLASH);
<span style="white-space:pre">	</span>return pathWithName.substring(index + 1);
    }
    catch (Exception e) {
<span style="white-space:pre">	</span>return pathWithName;
    }
}

測試用例代碼: 

@Test
public void testGetPayloadName() throws Exception {
    FileItem item = PowerMockito.mock(FileItem.class);
    String filePath = "../../../test/updates/Citrix.ibr";
    PowerMockito.when(item.getFilePath()).thenReturn(filePath);
    PowerMockito.when(item, "getPayloadName").thenCallRealMethod();
    assertEquals("Citrix.ibr", item.getPayloadName());
}
說明:當使用mock出來的對象去調用某個方法時,要對該方法使用thenCallRealMethod().

- whenNew

測試目標代碼:
public class ClassUnderTest {
    public boolean callInternalInstance(String path) { 
        File file = new File(path); 
        return file.exists(); 
    } 
}
測試用例代碼:    
@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassUnderTest.class)
public class TestClassUnderTest {
    @Test  
    public void testCallInternalInstance() throws Exception { 
        File file = PowerMockito.mock(File.class); 
        ClassUnderTest underTest = new ClassUnderTest(); 
        PowerMockito.whenNew(File.class).withArguments("bing").thenReturn(file); 
        PowerMockito.when(file.exists()).thenReturn(true); 
        Assert.assertTrue(underTest.callInternalInstance("bing")); 
    }
}
說明:當使用PowerMockito.whenNew方法時,必須加@PrepareForTest和@RunWith註解。註解@PrepareForTest裏寫的類是需要mock的new對象代碼所在的類。

- Mock final方法

測試目標代碼:

public class ClassUnderTest{
    public boolean callFinalMethod(Dependency d){
<span style="white-space:pre">	</span>return d.isAlive();
    }
}
class Dependency{
    public final boolean isAlive(){
<span style="white-space:pre">	</span>// do something
<span style="white-space:pre">	</span>return true;
    }
}

測試用例代碼:

@RunWith(PowerMockRunner.class) 
public class TestClassUnderTest {
    @Test 
    @PrepareForTest(Dependency.class) 
    public void testCallFinalMethod() {
        Dependency depencency =  PowerMockito.mock(Dependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(depencency.isAlive()).thenReturn(true);
        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }
}
 說明: 當需要mock final方法的時候,必須加@PrepareForTest和@RunWith註解,@PrepareForTest裏寫的類是final方法所在的類。
 

- Mock私有方法

測試目標代碼:
public class ClassUnderTest{
    public boolean callPrivateMethod(){
<span style="white-space:pre">	</span>return isAlive();
    }
    private boolean isAlive(){
<span style="white-space:pre">	</span>// do something
<span style="white-space:pre">	</span>return true;
    }
}
測試用例代碼:
@RunWith(PowerMockRunner.class) 
@PrepareForTest(ClassUnderTest.class) 
public class TestClassUnderTest {
    @Test 
    public void testCallFinalMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
<span style="white-space:pre">	</span>PowerMockito.when(underTest.callPrivateMethod().thenCallRealMethod();
        PowerMockito.when(underTest, "isAlive").thenReturn(true);
        Assert.assertTrue(underTest.callPrivateMethod());
    }
}
說明: 和mock final方法一樣,必須加@PrepareForTest和@RunWith註解,@PrepareForTest裏寫的類是private方法所在的類。

- Mock靜態方法

測試目標代碼:
public class ClassUnderTest{
    public boolean callStaticMethod(Dependency d){
<span style="white-space:pre">	</span>return Dependency.isExist();
    }
}
class Dependency{
    public static boolean isExist(){
<span style="white-space:pre">	</span>// do something
<span style="white-space:pre">	</span>return true;
    }
}
測試用例代碼:
@RunWith(PowerMockRunner.class) 
public class TestClassUnderTest {
    @Test 
    @PrepareForTest(Dependency.class) 
    public void testCallFinalMethod() {
        PowerMockito.mockStatic(Dependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(Dependency.isExist()).thenReturn(true);
        Assert.assertTrue(underTest.callStaticMethod(depencency));
    }
}
說明: mock靜態方法時需要加@PrepareForTest和@RunWith註解,@PrepareForTest註解中是靜態方法所在的類。

- suppress

測試目標代碼:
public class RefreshMgmt{
    private static final String MSG_DOWNLOAD_FAILED = Messages.getString("RefreshThread.0");
    public boolean downloadFiles(String path) {
        return download(path);
    }
    private boolean download(String localPath){
<span style="white-space:pre">	</span>// do something
<span style="white-space:pre">	</span>return false;
    }
}
測試用例代碼:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ RefreshMgmt.class, Messages.class })
public class TestRefreshMgmt {
    @Test
    public void testDownloadFiles() throws Exception {
        PowerMockito.suppress(PowerMockito.method(Messages.class, "getString",  String.class));
<span style="white-space:pre">	</span>//PowerMockito.suppress(PowerMockito.field(RefreshMgmt.class,  "MSG_DOWNLOAD_FAILED");
        RefreshMgmt mgmt = PowerMockito.mock(RefreshMgmt.class);
        PowerMockito.when(mgmt, "download", Matchers.anyString()).thenReturn(true);
        PowerMockito.when(mgmt.downloadFiles(Matchers.anyString())).thenCallRealMethod();
        assertTrue(mgmt.downloadFiles("C:/temp"));
    }
}
說明: PowerMockito.suppress方法原來禁用某個域或方法,在本例中初始化Messages類會拋出空指針異常,因
此用suppress方法來跳過這個field的初始化。
  • PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class))表示禁用BaseEntity的構造函數
  • PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class, String.class,  Integer.class))表示禁用參數爲String和Integer類型的BaseEntity構造方法。
  • PowerMockito.suppress(PowerMockito.method(BaseEntity.class, "performAudit", String.class))表示禁用BaseEntity的performAudit方法。
  • @SuppressStaticInitializationFor("BaseEntity")表示禁用BaseEntity的靜態初始化。注意引號部分通常需要全名,比如"com.gitshah.powermock.BaseEntity"。
  • PowerMockito.suppress(PowerMockito.field(BaseEntity.class,"identifier")):禁用identifier域。

- WhiteBox

測試目標代碼:
public class CataElement{
    private boolean isAvailable = false;
    private List<FileItem> items = new ArrayList<>();
    private Date parseDate(String date) {
    if(!isAvailable)
<span style="white-space:pre">	</span>return null;
        SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
        try {
            if (date == null || date.isEmpty())
                return null;
            return sdf.parse(date);
        }
        catch (ParseException e) {
            Log.log(Log.WARN, e, getClass());
            return null;
        }
    }
}
測試用例代碼:
public class CataElementTest{
    @Test
    public void test(){
        CataElement e = new CataElement();
        Whitebox.setInternalState(e, "isAvailable", true);
        // other things
<span style="white-space:pre">	</span>List<FileItem> items = Whitebox.getInternalState(e, "items");
<span style="white-space:pre">	</span>assertTrue(items.size == 0);
    }
}
  • Whitebox.setInternalState(object, "fieldName", value)可以設置某個對象的某個field。
  • Whitebox.getInternalState(object, "fieldName")獲取某個對象的某個field的值。
  • Whitebox.invokeMethod(object, methodName, para)可以調用私有方法,測試私有方法的返回值。

- Answer

測試用例代碼:
@Test
public void test() {
    final EmployeeService mock = PowerMockito.mock(EmployeeService.class);
    final Employee employee = new Employee();
    PowerMockito.when(mock.findEmployeeByEmail(Matchers.anyString())).then(new Answer<Employee>() {
<span style="white-space:pre">	</span>public Employee answer(InvocationOnMock invocation) throws Throwable {
<span style="white-space:pre">	</span>    final String email = (String) invocation.getArguments()[0];
<span style="white-space:pre">	</span>    if(email == null) return null;
<span style="white-space:pre">	</span>    if(email.startsWith("deep")) return employee;
<span style="white-space:pre">	</span>    if(email.endsWith("packtpub.com")) return employee;
<span style="white-space:pre">	</span>    return null;
<span style="white-space:pre">	</span>}
    });
    final EmployeeController employeeController = new EmployeeController(mock);
    assertSame(employee, employeeController.findEmployeeByEmail("[email protected]"));
    assertSame(employee, employeeController.findEmployeeByEmail("[email protected]"));
    assertNull(employeeController.findEmployeeByEmail("[email protected]"));
}
說明:改測試方法根據不同的參數返回不同的結果,Answer的泛型類型必須和answer方法的返回值類型一致。
Answer接口指定執行的action和返回值。Answer的參數是InvocationOnMock的實例,支持:
callRealMethod():調用真正的方法
getArguments():獲取所有參數
getMethod():返回mock實例調用的方法
getMock():獲取mock實例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章