简介
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final类和方法、私有方法、去除静态初始化器等等。
一、环境准备
1.1. 引入PowerMock包
为了引入PowerMock包,需要在pom.xml文件中加入下列maven依赖:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
集成mvc/普通maven项目
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
集成springboot项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
二、PowerMock相关讲解
2.1 一个简单的例子
这里,用List举例,模拟一个不存在的列表,但是返回的列表大小为100。
public class ListTest {
@Test
public void testSize() {
Integer expected = 100;
List list = PowerMockito.mock(List.class);
PowerMockito.when(list.size()).thenReturn(expected);
Integer actual = list.size();
Assert.assertEquals("返回值不相等", expected, actual);
}
}
2.2 mock方法
声明:
T PowerMockito.mock(Class clazz);
用途:
可以用于模拟指定类的对象实例。
-
当模拟非final类(接口、普通类、虚基类)的非final方法时,不必使用@RunWith和@PrepareForTest注解。
-
当模拟final类或final方法时,必须使用@RunWith和@PrepareForTest注解。注解形如:
@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})
2.2.1 模拟非final类普通方法
@Getter
@Setter
@ToString
public class Rectangle implements Sharp {
private double width;
private double height;
@Override
public double getArea() {
return width * height;
}
}
public class RectangleTest {
@Test
public void testGetArea() {
double expectArea = 100.0D;
Rectangle rectangle = PowerMockito.mock(Rectangle.class);
PowerMockito.when(rectangle.getArea()).thenReturn(expectArea);
double actualArea = rectangle.getArea();
Assert.assertEquals("返回值不相等", expectArea, actualArea, 1E-6D);
}
}
2.2.2 模拟final类或final方法
@Getter
@Setter
@ToString
public final class Circle {
private double radius;
public double getArea() {
return Math.PI * Math.pow(radius, 2);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({Circle.class})
public class CircleTest {
@Test
public void testGetArea() {
double expectArea = 3.14D;
Circle circle = PowerMockito.mock(Circle.class);
PowerMockito.when(circle.getArea()).thenReturn(expectArea);
double actualArea = circle.getArea();
Assert.assertEquals("返回值不相等", expectArea, actualArea, 1E-6D);
}
}
2.2. mockStatic方法
声明:
PowerMockito.mockStatic(Class clazz);
用途:
可以用于模拟类的静态方法,必须使用“@RunWith”和“@PrepareForTest”注解。
@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
@Test
public void testIsEmpty() {
String string = "abc";
boolean expected = true;
PowerMockito.mockStatic(StringUtils.class);
PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(expected);
boolean actual = StringUtils.isEmpty(string);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
3.1. spy类
如果一个对象,我们只希望模拟它的部分方法,而希望其它方法跟原来一样,可以使用PowerMockito.spy方法代替PowerMockito.mock方法。于是,通过when语句设置过的方法,调用的是模拟方法;而没有通过when语句设置的方法,调用的是原有方法。
声明:
PowerMockito.spy(Class clazz);
用途:
用于模拟类的部分方法。
案例:
public class StringUtils {
public static boolean isNotEmpty(final CharSequence cs) {
return !isEmpty(cs);
}
public static boolean isEmpty(final CharSequence cs) {
return cs == null || cs.length() == 0;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
@Test
public void testIsNotEmpty() {
String string = null;
boolean expected = true;
PowerMockito.spy(StringUtils.class);
PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(!expected);
boolean actual = StringUtils.isNotEmpty(string);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
声明:
T PowerMockito.spy(T object);
用途:
用于模拟对象的部分方法。
案例:
public class UserService {
private Long superUserId;
public boolean isNotSuperUser(Long userId) {
return !isSuperUser(userId);
}
public boolean isSuperUser(Long userId) {
return Objects.equals(userId, superUserId);
}
}
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Test
public void testIsNotSuperUser() {
Long userId = 1L;
boolean expected = false;
UserService userService = PowerMockito.spy(new UserService());
PowerMockito.when(userService.isSuperUser(userId)).thenReturn(!expected);
boolean actual = userService.isNotSuperUser(userId);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
4.1. when().thenReturn()模式
声明:
PowerMockito.when(mockObject.someMethod(someArgs)).thenReturn(expectedValue);
PowerMockito.when(mockObject.someMethod(someArgs)).thenThrow(expectedThrowable);
PowerMockito.when(mockObject.someMethod(someArgs)).thenAnswer(expectedAnswer);
PowerMockito.when(mockObject.someMethod(someArgs)).thenCallRealMethod();
用途:
用于模拟对象方法,先执行原始方法,再返回期望的值、异常、应答,或调用真实的方法。
4.1.1. 返回期望值
public class ListTest {
@Test
public void testGet() {
int index = 0;
Integer expected = 100;
List<Integer> mockList = PowerMockito.mock(List.class);
PowerMockito.when(mockList.get(index)).thenReturn(expected);
Integer actual = mockList.get(index);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
4.1.2. 返回期望异常
public class ListTest {
@Test(expected = IndexOutOfBoundsException.class)
public void testGet() {
int index = -1;
Integer expected = 100;
List<Integer> mockList = PowerMockito.mock(List.class);
PowerMockito.when(mockList.get(index)).thenThrow(new IndexOutOfBoundsException());
Integer actual = mockList.get(index);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
5 模拟私有方法
5.1.1 通过when实现
public class UserService {
private Long superUserId;
public boolean isNotSuperUser(Long userId) {
return !isSuperUser(userId);
}
private boolean isSuperUser(Long userId) {
return Objects.equals(userId, superUserId);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {
@Test
public void testIsNotSuperUser() throws Exception {
Long userId = 1L;
boolean expected = false;
UserService userService = PowerMockito.spy(new UserService());
PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);
boolean actual = userService.isNotSuperUser(userId);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
5.1.2 通过stub实现
通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法的返回值,而不能模拟指定参数的返回值。
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {
@Test
public void testIsNotSuperUser() throws Exception {
Long userId = 1L;
boolean expected = false;
UserService userService = PowerMockito.spy(new UserService());
PowerMockito.stub(PowerMockito.method(UserService.class, "isSuperUser", Long.class)).toReturn(!expected);
boolean actual = userService.isNotSuperUser(userId);
Assert.assertEquals("返回值不相等", expected, actual;
}
}
5.1.3 测试私有方法
@RunWith(PowerMockRunner.class)
public class UserServiceTest9 {
@Test
public void testIsSuperUser() throws Exception {
Long userId = 1L;
boolean expected = false;
UserService userService = new UserService();
Method method = PowerMockito.method(UserService.class, "isSuperUser", Long.class);
Object actual = method.invoke(userService, userId);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
5.1.4 验证私有方法
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest10 {
@Test
public void testIsNotSuperUser() throws Exception {
Long userId = 1L;
boolean expected = false;
UserService userService = PowerMockito.spy(new UserService());
PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);
boolean actual = userService.isNotSuperUser(userId);
PowerMockito.verifyPrivate(userService).invoke("isSuperUser", userId);
Assert.assertEquals("返回值不相等", expected, actual);
}
}
6. 私有属性设定
Whitebox.setInternalState方法
现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。
@Service
public class UserService {
@Value("${system.userLimit}")
private Long userLimit;
public Long getUserLimit() {
return userLimit;
}
}
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@InjectMocks
private UserService userService;
@Test
public void testGetUserLimit() {
Long expected = 1000L;
Whitebox.setInternalState(userService, "userLimit", expected);
Long actual = userService.getUserLimit();
Assert.assertEquals("返回值不相等", expected, actual);
}
}