一、简介
- EasyMock、Mockito、jMock(单元测试模拟框架)
在有这些模拟框架之前,程序员为了编写某一个函数的单元测试,必须进行十分繁琐的初始化工作,以确保调用的接口或编写的代码得到预期的结果。单元测试模拟框架极大的简化了单元测试的编写过程,在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。
**缺点:**都不可以实现对静态函数、构造函数、私有函数、Final函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能 - Power Mock
PowerMock 是在EasyMock以及Mockito基础上的扩展,通过定制类加载等技术,PowerMock实现了之前提到的所有模拟功能,使其成为大型系统上单元测试中的必备工具。 - Mock
Mock的意思是模拟,就是模拟接口返回的信息,用已有的信息替换它需要返回的信息,从而实现对上级模块的测试、
二、依赖
<properties>
<powermock.version>1.6.6</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-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
三、相关注解
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserController.class, CommonUtils.class})
@PowerMockIgnore("javax.management.*")
public class UserController {
@Mock
private UserService userService;
@InjectMocks
private UserController userController = new UserController();
}
- @RunWith(PowerMockRunner.class)
表明用PowerMockerRunner来测试用例,否则无法使用PowerMock - @PrepareForTest({UserController.class, CommonUtils.class})
所有需要测试的类,列在此处,以逗号分隔;在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类 - @PowerMockIgnore(“javax.management.*”)
为了解决使用powermock后,提示classloader错误 - @Mock
注解修饰会mock出来一个userService对象 - @InjectMocks
注解会主动将已存在的mock对象注入到bean中,按名称注入,这个注解修饰在我们需要测试的类上。必须要手动new一个实例,不然单元测试会有问题。
四、使用(示例代码在文末)
- PowerMockito.mock():
指定需要mock的类型类(接口或者实现类),生成Mock类,其中所有的方法都不是真实的方法,访问对象方法不会执行具体逻辑,而且返回值都是NULL或者Empty。
使用打桩返回给定数据:PowerMockito.when().thenReturn()方法
若要执行具体逻辑:PowerMockito.when().thenCallRealMethod()方法 - PowerMockito.spy():
spy机制可以监视一个真实对象,对其进行方法调用会执行真实逻辑;
spy也可以打桩指定的方法。
spy中使用都Return…when打桩,不会执行具体逻辑
spy中使用when…thenReturn打桩,会执行具体逻辑 - mock有返回值的普通方法
@Test
public void test_addUser(){
User user = new User();
PowerMockito.when(userService.addUser(user)).thenReturn(1);
boolean result = userController.addUser(user);
Assert.assertEquals(result, true);
}
注:userService.addUser()和userController.addUser()所使用的参数值必须保持一致
- mock无返回值的普通方法
方法一:
@Test
public void test_initUser() throws Exception {
User user = new User();
user.setId(111);
PowerMockito.doNothing().when(userService, "initUser", user);
User result = userController.initUser(user);
Assert.assertEquals(result.getId(), 111);
}
方法二:
@Test
public void test_initUser1() throws Exception {
User user = new User();
user.setId(111);
PowerMockito.doNothing().when(userService).initUser(user);
User result = userController.initUser(user);
Assert.assertEquals(result.getId(), 111);
}
- mock抛异常
@Test
public void test_delUser() throws Exception {
PowerMockito.when(userService.delUser(1)).thenThrow(new Exception("删除失败"));
boolean result = userController.delUser(1);
Assert.assertEquals(result, false);
}
注:thenThrow()中抛出delUser()方法抛出的异常或其子异常
- mock静态方法
@Test
public void test_append() {
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.when(CommonUtils.isNotEmpty("World")).thenReturn(false);
String result = userController.append("World");
Assert.assertEquals(result, "Hello");
}
注:需要在@PrepareForTest注解中加上CommonUtils.class
- mock返回值为void的static方法
方法一:
@Test
public void test_getFile() throws Exception {
String filePath = "xiaofeng";
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.doNothing().when(CommonUtils.class, "validFilePath", filePath);
File result = userController.getFile(filePath);
Assert.assertEquals(result.getName(), filePath);
}
方法二:
@Test
public void test_getFile1() throws Exception {
String filePath = "xiaofeng";
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.doNothing().when(CommonUtils.class);
CommonUtils.validFilePath(filePath);
File result = userController.getFile(filePath);
Assert.assertEquals(result.getName(), filePath);
}
- mock私有方法
方法一:
@Test
public void test_modUser() throws Exception {
User user = new User();
PowerMockito.when(userService.modUser(user)).thenReturn(1);
UserController controller = PowerMockito.mock(UserController.class);
// 给没有set方法的私有属性赋值
Whitebox.setInternalState(controller, "userService", userService);
// 因为要测试的是modUser()方法,所以调用这个方法时,应调用真实方法,并非mock掉的方法
PowerMockito.when(controller.modUser(user)).thenCallRealMethod();
// 在modUser()方法中会调用verifyMod()这个私有方法,所以将其mock掉
PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
boolean result = controller.modUser(user);
Assert.assertEquals(result, false);
}
注:此处的controller是mock出来的,不是UserControllerTest类中的成员变量userController(不会执行verifyMod方法,直接返回模拟值)
方法二:
@Test
public void test_modUser1() throws Exception {
User user = new User();
PowerMockito.when(userService.modUser(user)).thenReturn(1);
// 对userController进行监视
UserController controller = PowerMockito.spy(userController);
// 当userController的verifyMod被执行时,将被mock掉
PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
boolean result = controller.modUser(user);
Assert.assertEquals(result, false);
}
注:spy方法可以避免执行被测类中的成员函数,即mock掉不想被执行的私有方法(会执行verifyMod方法,但是会返回模拟值)
- 测试私有方法(注意:是测试,不是mock)
方法一:
@Test
public void test_verifyMod() throws Exception {
// 获取method方法
Method method = PowerMockito.method(UserController.class, "verifyMod", Integer.class);
// 调用Method的invoke方法来执行
boolean result = (boolean) method.invoke(userController, 1);
Assert.assertEquals(result, true);
}
方法二:
@Test
public void test_verifyMod1() throws Exception {
// 直接通过Whitebox来执行
boolean result = Whitebox.invokeMethod(userController, "verifyMod", 1);
Assert.assertEquals(result, true);
}
- mock新建对象
@Test
public void test_getUser() throws Exception {
User user = new User();
user.setName("大风");
PowerMockito.whenNew(User.class).withNoArguments().thenReturn(user);
User result = userController.getUser();
Assert.assertEquals(result.getName(), "大风");
}
- mock同一方法,返回不同的值
@Test
public void test_getString() throws Exception {
UserController controller = PowerMockito.spy(userController);
PowerMockito.when(controller, "getFlag").thenReturn(true, false);
String result = controller.getString("Hello");
Assert.assertEquals(result, "Hello");
}
- mock final方法
final与普通方法一样mock,但是需要将其所在class添加到@PrepareForTest注解中 - 参数的模糊匹配
org.mockito.Matchers类中,提供了很多any*的方法,如anyObject()、anyString()…我们可以使用这些方法去避免构建那些难以模拟的输入参数
注:如果对某一个参数使用了Matcher,那么这个方法的所有其它参数也必须使用Matcher,否则将会报错。
五、示例代码
package com.example.pattren.junitTest;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.example.pattren.junitTest;
public interface UserService {
int addUser(User user);
void initUser(User user);
int delUser(int id) throws Exception;
int modUser(User user);
}
package com.example.pattren.junitTest;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.text.Normalizer;
public class CommonUtils {
public static boolean isEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
}
public static boolean isNotEmpty(CharSequence cs) {
return !isEmpty(cs);
}
public static void validFilePath(String filePath) throws IOException {
if (CommonUtils.isEmpty(filePath)) {
throw new IOException("the file path is empty");
}
filePath = Normalizer.normalize(filePath, Normalizer.Form.NFKC);
File file = new File(filePath);
String decode = URLDecoder.decode(file.getCanonicalPath(), "UTF-8");
file = new File(decode);
if (!file.exists()) {
throw new IOException("the file path is not exists");
}
}
}
package com.example.pattren.junitTest;
import java.io.File;
import java.io.IOException;
public class UserController {
private UserService userService;
public boolean addUser(User user) {
int i = userService.addUser(user);
if (i <= 0) {
return false;
} else {
return true;
}
}
public User initUser(User user) {
userService.initUser(user);
return user;
}
public boolean delUser(int id) {
try {
userService.delUser(id);
return true;
} catch (Exception e) {
return false;
}
}
public String append(String param) {
String str = "Hello";
if (CommonUtils.isNotEmpty(param)) {
return str + param;
} else {
return str;
}
}
public File getFile(String filePath) throws IOException {
CommonUtils.validFilePath(filePath);
return new File(filePath);
}
public boolean modUser(User user) {
int flag = userService.modUser(user);
return verifyMod(flag);
}
private boolean verifyMod(Integer flag) {
if (flag > 0) {
return true;
}
return false;
}
public User getUser() {
User user = new User();
return user;
}
public String getString(String str) {
StringBuffer result = new StringBuffer();
while (this.getFlag()) {
result.append(str);
}
return result.toString();
}
private boolean getFlag() {
return true;
}
}
package com.example.pattren.junitTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import java.io.File;
import java.lang.reflect.Method;
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserController.class, CommonUtils.class})
@PowerMockIgnore("javax.management.*")
public class UserControllerTest {
@Mock
private UserService userService;
@InjectMocks
private UserController userController = new UserController();
/**
* mock有返回值的普通方法
*/
@Test
public void test_addUser() {
User user = new User();
PowerMockito.when(userService.addUser(user)).thenReturn(1);
boolean result = userController.addUser(user);
Assert.assertEquals(result, true);
}
/**
* mock无返回值的普通方法 (方法一)
*/
@Test
public void test_initUser() throws Exception {
User user = new User();
user.setId(111);
PowerMockito.doNothing().when(userService, "initUser", user);
User result = userController.initUser(user);
Assert.assertEquals(result.getId(), 111);
}
/**
* mock有无返回值的普通方法 (方法二)
*/
@Test
public void test_initUser1() throws Exception {
User user = new User();
user.setId(111);
PowerMockito.doNothing().when(userService).initUser(user);
User result = userController.initUser(user);
Assert.assertEquals(result.getId(), 111);
}
/**
* mock抛异常
*/
@Test
public void test_delUser() throws Exception {
PowerMockito.when(userService.delUser(1)).thenThrow(new Exception("删除失败"));
boolean result = userController.delUser(1);
Assert.assertEquals(result, false);
}
/**
* mock静态方法
*/
@Test
public void test_append() {
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.when(CommonUtils.isNotEmpty("World")).thenReturn(false);
String result = userController.append("World");
Assert.assertEquals(result, "Hello");
}
/**
* mock返回值为void的static方法 (方法一)
*/
@Test
public void test_getFile() throws Exception {
String filePath = "xiaofeng";
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.doNothing().when(CommonUtils.class, "validFilePath", filePath);
File result = userController.getFile(filePath);
Assert.assertEquals(result.getName(), filePath);
}
/**
* mock返回值为void的static方法 (方法二)
*/
@Test
public void test_getFile1() throws Exception {
String filePath = "xiaofeng";
PowerMockito.mockStatic(CommonUtils.class);
PowerMockito.doNothing().when(CommonUtils.class);
CommonUtils.validFilePath(filePath);
File result = userController.getFile(filePath);
Assert.assertEquals(result.getName(), filePath);
}
/**
* mock私有方法 (方法一)
*/
@Test
public void test_modUser() throws Exception {
User user = new User();
PowerMockito.when(userService.modUser(user)).thenReturn(1);
UserController controller = PowerMockito.mock(UserController.class);
// 给没有set方法的私有属性赋值
Whitebox.setInternalState(controller, "userService", userService);
// 因为要测试的是modUser()方法,所以调用这个方法时,应调用真实方法,并非mock掉的方法
PowerMockito.when(controller.modUser(user)).thenCallRealMethod();
// 在modUser()方法中会调用verifyMod()这个私有方法,所以将其mock掉
PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
boolean result = controller.modUser(user);
Assert.assertEquals(result, false);
}
/**
* mock私有方法 (方法二)
*/
@Test
public void test_modUser1() throws Exception {
User user = new User();
PowerMockito.when(userService.modUser(user)).thenReturn(1);
// 对userController进行监视
UserController controller = PowerMockito.spy(userController);
// 当userController的verifyMod被执行时,将被mock掉
PowerMockito.when(controller, "verifyMod", 1).thenReturn(false);
boolean result = controller.modUser(user);
Assert.assertEquals(result, false);
}
/**
* 测试私有方法(注意:是测试,不是mock) (方法一)
*/
@Test
public void test_verifyMod() throws Exception {
// 获取method方法
Method method = PowerMockito.method(UserController.class, "verifyMod", Integer.class);
// 调用Method的invoke方法来执行
boolean result = (boolean) method.invoke(userController, 1);
Assert.assertEquals(result, true);
}
/**
* 测试私有方法(注意:是测试,不是mock) (方法二)
*/
@Test
public void test_verifyMod1() throws Exception {
// 直接通过Whitebox来执行
boolean result = Whitebox.invokeMethod(userController, "verifyMod", 1);
Assert.assertEquals(result, true);
}
/**
* mock新建对象
*/
@Test
public void test_getUser() throws Exception {
User user = new User();
user.setName("大风");
PowerMockito.whenNew(User.class).withNoArguments().thenReturn(user);
User result = userController.getUser();
Assert.assertEquals(result.getName(), "大风");
}
/**
* mock同一方法,返回不同的值
*/
@Test
public void test_getString() throws Exception {
UserController controller = PowerMockito.spy(userController);
PowerMockito.when(controller, "getFlag").thenReturn(true, false);
String result = controller.getString("Hello");
Assert.assertEquals(result, "Hello");
}
}