PowerMock从入门到放弃再到使用

本文详细介绍了PowerMock工具的使用方法及其实现原理。PowerMock能够解决Mockito无法模拟静态、final和私有方法的问题,并提供了丰富的注解和API来辅助单元测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在做单元测试的时候,可能会用到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();

    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值