简介
Mockito无法实现对静态函数、构造函数、私有函数、Final 等函数的模拟,PowerMock是一个Java模拟框架,可用于解决通常被认为很难甚至无法测试的测试问题。 使用PowerMock,可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖注入等等。 PowerMock通过在执行测试时在运行时修改字节代码来完成这些技巧。
本文主要是介绍PowerMock 在 Mockito 框架上扩展的功能。
PowerMock提供了一个名为“PowerMockito”的类,用于创建模拟/对象/类并验证,其他仍然可以使用Mockito来设置和验证(例如times(),anyInt())。
maven使用配置
对junit来说有版本不同的话,配置也不相同,我们这里只给出junit4.4以上版本,mockito2.x版本的maven配置,其他的可以参考github官方介绍:https://github.com/powermock/powermock/wiki/Getting-Started
<properties>
<powermock.version>1.7.1</powermock.version>
</properties>
<dependencies>
<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-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
所有用到PowerMock的类都需要@RunWith(PowerMockRunner.class)和@PrepareForTest注解。
1. mock静态方法
(1) 首先在测试类上添加@PrepareForTest注解
@PrepareForTest(Static.class) // Static.class 是包含静态方法的类。
(2) 调用PowerMockito.mockStatic()方法来模拟Static.class
PowerMockito.mockStatic(Static.class);
(3) 使用Mockito.when()来模拟期望的结果
Mockito.when(Static.firstStaticMethod(param)).thenReturn(value);
(4) 验证
第一步:调用PowerMockito.verifyStatic(Static.class)验证行为;
第二步:然后调用Static.class的静态方法进行验证
PowerMockito.verifyStatic(Static.class); // 1
Static.firstStaticMethod(param); // 2
下面给一个完整的示例:
定义一个包含静态方法的类Hello:
public class Hello {
public static int getValue1(){
return 1;
}
public static int getValue2(){
return 2;
}
}
下面是测试代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Hello.class)
public class PowerMockTest {
@Test
public void testGetValue() {
PowerMockito.mockStatic(Hello.class);
Mockito.when(Hello.getValue1()).thenReturn(444);
Mockito.when(Hello.getValue2()).thenReturn(666);
assertEquals(444, Hello.getValue1());
assertEquals(666, Hello.getValue2());
}
}
2. mock构造方法
比如有一个要测试的类Directory :
public class Directory {
public boolean create(String path) {
File file = new File(path);
if (file.exists()) {
throw new IllegalArgumentException("path already exists");
}
return file.mkdirs();
}
}
为了测试create方法,我们可以对File进行mock,并对file的方法进行打桩,先看一下测试代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Directory.class)
public class DirectoryTest {
@Test
public void testCreate() throws Exception {
String path = "mocked path";
File mockFile = PowerMockito.mock(File.class);
PowerMockito.whenNew(File.class).withArguments(path).thenReturn(mockFile);
PowerMockito.when(mockFile.exists()).thenReturn(false);
PowerMockito.when(mockFile.mkdirs()).thenReturn(true);
assertTrue(new Directory().create(path));
}
}
大致步骤如下
(1)通过PowerMockito.mock(File.class)来mock一个File对象;
(2)使用 whenNew().withArguments().thenReturn()
语句实现对具体类的构造函数的模拟操作;
(3)然后对于之前创建的模拟对象 mockFile
使用 When().thenReturn()
语句,即可实现需要的所有功能,从而实现对被测对象的覆盖测试。
3. mock私有方法和静态方法
为了实现对类的私有方法或者是 Final 方法的模拟操作,需要 PowerMock 提供的另外一项技术:局部模拟。
在之前的介绍的模拟操作中,我们总是去模拟一整个类或者对象,然后使用 When().thenReturn()
语句去指定其中值得关心的部分函数的返回值,从而达到搭建各种测试环境的目标。对于没有使用 When().thenReturn()
方法指定的函数,系统会返回各种类型的默认值(具体值可参考官方文档)。
局部模拟则提供了另外一种方式,在使用局部模拟时,被创建出来的模拟对象依然是原系统对象,虽然可以使用方法 When().thenReturn()
来指定某些具体方法的返回值,但是没有被用此函数修改过的函数依然按照系统原始类的方式来执行。
这种局部模拟的方式的强大之处在于,除开一般方法可以使用之外,Final 方法和私有方法一样可以使用。
参考如下所示的被测代码:
public final class PrivatePartialMockingExample {
public String methodToTest() {
return methodToMock("input");
}
private String methodToMock(String input) {
return "REAL VALUE = " + input;
}
}
为了保持单元测试的纯洁性,在测试方法 methodToTest()
时,我们不希望受到私有函数 methodToMock()
实现的干扰,为了达到这个目的,我们使用刚提到的局部模拟方法来实现 , 实现方式如下:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PrivatePartialMockingExampleTest {
@Test
public void demoPrivateMethodMocking() throws Exception {
final String expected = "TEST VALUE";
final String nameOfMethodToMock = "methodToMock";
final String input = "input";
PrivatePartialMockingExample underTest = spy(new PrivatePartialMockingExample());
/* Setup the expectation to the private method using the method name */
when(underTest, nameOfMethodToMock, input).thenReturn(expected);
assertEquals(expected, underTest.methodToTest());
// Optionally verify that the private method was actually called
verifyPrivate(underTest).invoke(nameOfMethodToMock, input);
}
}
可以发现,为了实现局部模拟操作,用来创建模拟对象的函数从 mock()
变成了 spy()
,操作对象也从类本身变成了一个具体的对象。同时,When()
函数也使用了不同的版本:在模拟私有方法或者是 Final 方法时,When()
函数需要依次指定模拟对象、被指定的函数名字以及针对该函数的输入参数列表。
后半部分摘自:https://www.ibm.com/developerworks/cn/java/j-lo-powermock/index.html