在做单元测试的时候,可能会用到Mockito来测试,但是Mockito也有不足,不能mock静态、final、私有方法等。而PowerMock能够完美的弥补Mockito的不足。
PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。目前PowerMock支持EasyMock和Mockito。
除了PowerMock这个工具之外,还有JMockit,这个工具比PowerMock更强悍,对比结果可以参看https://blog.youkuaiyun.com/yasi_xi/article/details/24642517,关于JMockit的介绍可以移步:https://blog.youkuaiyun.com/zero__007/article/details/49280827。
需要的jar包:
testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '1.7.4'
testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '1.7.4'
PowerMock有两个重要的注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClass.class })
当需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
@InjectMocks
:创建一个实例,用@Mock或@Spy注解创建的mock将被注入到该实例中。
@Mock
:创建一个mock,该对象所有的方法被置空,即都不是真实的方法。
@Spy
:创建一个mock对象,@Spy修饰的对象必须先手动new出来,但是,该对象所有方法都是真实方法,返回值都是和真实方法一样的。
需要注意@Mock和@Spy对象在方法mock时的区别:
对@Spy生成的类,用when去设置模拟返回值时,它里面的方法会先执行一次,例如:
PowerMockito.when(demo.say(ArgumentMatchers.anyString())).thenReturn("zero");
这种方式时,demo.say()方法会先调用一次,需要用如下方式:
// --- error
// PowerMockito.doReturn("zero").when(demo).sys(ArgumentMatchers.anyString());
// PowerMockito.doReturn("zero").when(demo.sys(ArgumentMatchers.anyString()));
// --- error
// Mockito.doReturn("zero").when(demo.sys(ArgumentMatchers.anyString()));
// correct
Mockito.doReturn("zero").when(demo).sys(ArgumentMatchers.anyString());
使用PowerMockito.doReturn会报错,不清楚为什么。用Mockito.doReturn没有问题。
以下是被测试的代码:
public class Demo {
@Autowired
private UserService userService;
public void dealUser() {
String name = userService.getUserName(1);
System.out.println("userService.getUserName(): " + name);
userService.handle(name);
say(name);
}
private void say(String name) {
System.out.println("say() : " + name);
sys(name);
}
public void work() {
System.out.println("work() ....");
}
public String getString() {
return Utils.getObject(this, "/zero");
}
public String sys(String name) {
System.out.println("sys() : " + name);
return name;
}
}
public class UserService {
public String getUserName(int id) {
return "hello";
}
public void handle(String s) {
System.out.println("handle(): ..." + s);
}
}
public class Utils {
private static String URL_STRING = "http://www.baidu.com.cn";
private static void deal() {
System.out.println("deal() " + URL_STRING);
}
public static String getObject(Demo object, String str) {
deal();
System.out.println("getObject() ");
return URL_STRING + str;
}
}
测试代码:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
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;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Demo.class, Utils.class})
@PowerMockIgnore("javax.management.*") //为了解决使用powermock后,提示classloader错误
public class DemoTest {
@Spy //需要手动new
@InjectMocks
private Demo demo = new Demo();
@Mock // 非@Spy修饰的对象,可以不手动new,由mockito框架实例化
private UserService userService;
/**
* 测试
* @see Demo#dealUser()
*/
@Test
public void testDealUser() throws Exception {
// correct
PowerMockito.when(userService.getUserName(ArgumentMatchers.anyInt())).thenReturn("zero007");
// error
// PowerMockito.doReturn("zero007").when(userService.getUserName(ArgumentMatchers.anyInt()));
// correct
PowerMockito.doReturn("zero007").when(userService).getUserName(ArgumentMatchers.anyInt());
// when 里面参数 如果方法返回值为void
// error
// PowerMockito.doNothing().when(userService.handle(ArgumentMatchers.anyString()));
// correct
PowerMockito.doNothing().when(userService).handle(ArgumentMatchers.anyString());
// correct
PowerMockito.doNothing().when(userService, "handle", ArgumentMatchers.anyString());
// mock私有方法,demo 必须是mock或者spy的实例
// 必须先录制预期行为
PowerMockito.doNothing().when(demo, "say", ArgumentMatchers.anyString());
demo.dealUser();
}
/**
* @see Demo#say(String)
*/
@Test
public void testSay() throws Exception {
// throw org.mockito.exceptions.misusing.UnfinishedStubbingException:
// PowerMockito.doReturn("zero").when(demo).sys(ArgumentMatchers.anyString());
// throw org.mockito.exceptions.misusing.UnfinishedStubbingException:
// PowerMockito.doReturn("zero").when(demo.sys(ArgumentMatchers.anyString()));
// correct
// Mockito.doReturn("zero").when(demo).sys(ArgumentMatchers.anyString());
// error Mockito.doReturn("zero").when(demo.sys(ArgumentMatchers.anyString()));
Mockito.when(demo.sys(ArgumentMatchers.anyString())).thenReturn("zero");
// ---sys() :
System.out.println(demo.sys("007"));
Whitebox.invokeMethod(demo, "say", "Hello World!");//测试private方法
}
/**
* @see Demo#work()
*/
@Test
public void testWork() throws Exception {
}
/**
* @see Demo#getString()
*/
@Test
public void tetsGetString() throws Exception {
//mock静态方法
PowerMockito.mockStatic(Utils.class);
PowerMockito.when(Utils.getObject(ArgumentMatchers.any(Demo.class), ArgumentMatchers.anyString())).thenCallRealMethod();
PowerMockito.doNothing().when(Utils.class, "deal");
// PowerMockito.when(Utils.xxx(xx).thenReturn(xxx));
Whitebox.setInternalState(Utils.class, "URL_STRING", "www.123.com");
String result = demo.getString();
System.out.println(result);
}
}
PowerMock简单实现原理
1.当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的
org.powermock.core.classloader.MockClassLoader
实例,然后加载该测试用例使用到的类(系统类除外)。
2.PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
3.如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
参考:
https://blog.youkuaiyun.com/xiaoxufox/article/details/78562656
https://www.jianshu.com/p/60309d71002d
补充:如https://blog.youkuaiyun.com/zero__007/article/details/50365311的测试场景,怎么用powermock实现呢?
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
public class WorkTest {
@Test
public void testTask() throws Exception {
Work work = PowerMockito.spy(new Work());
OtherVO otherVO = PowerMockito.mock(OtherVO.class);
PowerMockito.when(otherVO.getValue()).thenReturn("zero007");
Whitebox.setInternalState(work, "otherVO", otherVO);
ExecutorService executor = PowerMockito.mock(ThreadPoolExecutor.class);
PowerMockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Runnable command = invocationOnMock.getArgument(0);
command.run();
return null;
}
}).when(executor).execute(ArgumentMatchers.any(Runnable.class));
Whitebox.setInternalState(work, "executor", executor);
work.task();
}
}