PowerMock学习笔记(一)
Powermock扩展了EasyMock的功能。它使用定制的类加载器,通过字节码操作提供了对静态方法、构造方法、final类与final方法、以及私有方法的mock能力。
当前PowerMock支持EasyMock和Mockito。
一、bypass封装:
Whitebox类提供了一些可以帮你bypass封装的方法。
1)使用Whitebox.setInternalState(..)来设置实例或者是类的非public成员。
2)使用Whitebox.getInternalState()来获取实例或者是类的非public成员
3)使用Whitebox.invokeMethod(..)调用实例或者类的非公共方法
4)使用Whitebox.invokeConstructor(..)来创建带private构造方法的类的实例
eg:
1、访问内部状态
对于可变对象,其内部状态可能在某个方法调用后改变。当对这类对象进行单元测试时,如果有某种简单的方法可以持有这个状态并检测这个状态是否一致更新就最好了。PowerMock提供了多个有用的反射工具来支持这个功能,这些反射工具都在 org.powermock.reflect.Whitebox这个类中。
举个列子:我们有这样一个类
public class ServiceHolder {
private final Set<Object> services = new HashSet<Object>();
public void addService(Object service) {
services.add(service);
}
public void removeService(Object service) {
services.remove(service);
}
}
比如,我们要测试addService()方法,我们要确保ServiceHolder在addService方法被调用后其状态正确更新,也就是说,往services集合中添加了一个对象。一个办法是提供一个包private或者protected的方法,如getServices()来返回services,但是这样做的话,我们得向ServiceHolder中添加一个方法,而这么做仅仅只是为了让这个类具有可测试性。另一个可选的办法是使用Whitebox.getInternalState(..)方法来完成这个目的,而无需”制造“新的代码。在这个例子中,addService方法的可能如下:
package com.larry.junit;
import java.util.Set;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
@PrepareForTest({})
public class ServiceHolderTest {
@Test
public void testAddService() {
ServiceHolder tested = new ServiceHolder();
final Object service = new Object();
tested.addService(service);
//使用PowerMock访问私有域
Set<Object> services = Whitebox.getInternalState(tested, "services");
Assert.assertEquals("services size expected is: ", 1, services.size());
Assert.assertSame("services should contain the expected service", service,
services.iterator().next());
}
}
使用PowerMock1.0以及以上版本还可以通过指定域的类型来获取内部状态。在上面这个例子中,我们可以这样写:
package com.larry.junit;
import java.util.Set;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
@PrepareForTest({})
public class ServiceHolderTest {
@Test
public void testAddService() {
ServiceHolder tested = new ServiceHolder();
final Object service = new Object();
tested.addService(service);
//使用PowerMock访问私有域
// Set<Object> services = Whitebox.getInternalState(tested, "services");
Set<Object> services = Whitebox.getInternalState(tested, Set.class);
Assert.assertEquals("services size expected is: ", 1, services.size());
Assert.assertSame("services should contain the expected service", service, services.iterator().next());
}
}
这种做法更安全,基于这个原因也更推荐这样写。但是,如果某个类型有多个域,那么还是的使用第一种直接使用域的名称的方法。
2、设置对象内部状态
设置对象内部状态也很容易。假设有下面一个类:
public class ReportGenerator {
@Injectable
private ReportTemplateService reportTemplateService;
public Report generateReport(String reportId) {
String templateId = reportTemplateService.getTemplateId(reportId);
/*
* Imagine some other code here that generates the report based on the
* template id.
*/
return new Report("name");}
}
}
假设我们使用了一个依赖注入框架,它可以在运行时自动给我们提供ReportTemplateService类的一个实例。我们有多种选择测试这个类。例如,我们可以重构这个类使用构造方法或者setter注入,使用Java反射机制或者我们自己创建一个reportTemplateService的setter方法。另一个办法就是让PowerMock通过Whitebox.setInternalState(..)方法。
Whitebox.setInternalState(tested, "reportTemplateService", reportTemplateServiceMock);
3、调用私有方法
要调用私有方法,可以使用Whitebox.invokeMethod(..)方法。例如,myInstance实例中,我们有一个要测试的private方法sum():
private int sum(int a, int b) {
return a+b;
}
你只需要这样做:
int sum = Whitebox.<Integer> invokeMethod(myInstance, "sum", 1, 2);
sum的值将为3.
代码:
package com.larry.junit;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
public class SumTest {
@Test
public void testSum() throws Exception {
Sum myInstance = new Sum();
int result = Whitebox.invokeMethod(myInstance, "sum", 1, 2);
Assert.assertEquals(3, result);
}
}
大多情况下,这种做法运行良好。然而有些时候,使用这种方法PowerMock分不清楚它要调用的到底是哪个方法,可能测试结果与我们的预期不一致,例如我们有两个重载的方法:
package com.larry.junit;
public class Sum {
private int sum(int a, int b) {
return a + b;
}
private int sum(Integer a, Integer b) {
return a * b;
}
}
那么我们需要通过明确指定参数类型int来告知PowerMock调用方法。
package com.larry.junit;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
public class SumTest {
@Test
public void testSum() throws Exception {
Sum myInstance = new Sum();
int result = Whitebox.<Integer>invokeMethod(myInstance, "sum", new Class<?>[] {int.class, int.class}, 1, 2);
Assert.assertEquals(3, result);
}
}