Skip to content

PowerMock:当你需要 Mock 不可 Mock 的东西

PowerMock 是 Mockito 的扩展,能 Mock 一些 Mockito 原本搞不定的东西:静态方法、私有方法、构造函数。

但 PowerMock 的代价是:慢、侵入性强、对 JDK 版本敏感。不到万不得已,别用它。

什么场景需要 PowerMock

场景说明Mockito 能搞定吗
静态方法System.currentTimeMillis()Math.random()
私有方法想单独测试某个私有方法
构造函数new Date() 返回固定时间
枚举Mock 枚举值

能用 Mockito 搞定的,坚决不用 PowerMock。

Maven 依赖

xml
<!-- PowerMock + JUnit 4 -->
<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>

注意:PowerMock 目前对 JDK 17+ 支持有限,使用高版本 JDK 的项目需要特别谨慎。

Mock 静态方法

java
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.junit.runner.RunWith;

@RunWith(PowerMockRunner.class)
@PrepareForTest(StaticClass.class)  // 指定要 Mock 的静态类
class StaticMethodMockTest {

    @Test
    void testStaticMethod() {
        // Mock 静态方法
        PowerMockito.mockStatic(StaticClass.class);
        when(StaticClass.getValue()).thenReturn(100);

        // 调用
        int result = StaticClass.getValue();

        // 验证
        assertEquals(100, result);
        verifyStatic(StaticClass.class, times(1));
    }
}

Mock 私有方法

java
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
class PrivateMethodMockTest {

    @Test
    void testPrivateMethod() throws Exception {
        MyClass spy = PowerMockito.spy(new MyClass());

        // Mock 私有方法
        PowerMockito.doReturn("mocked").when(spy, "privateMethod");

        // 调用公共方法(内部会调用被 Mock 的私有方法)
        String result = spy.publicMethod();

        assertEquals("mocked", result);
    }
}

Mock 构造函数

java
@RunWith(PowerMockRunner.class)
class ConstructorMockTest {

    @Test
    void testConstructor() throws Exception {
        // 创建 mock 实例
        MyClass mockInstance = PowerMockito.mock(MyClass.class);
        when(mockInstance.getValue()).thenReturn("mocked");

        // Mock 构造函数,让 new MyClass() 返回 mock 实例
        PowerMockito.whenNew(MyClass.class).withNoArguments().thenReturn(mockInstance);

        // 调用 new MyClass()
        MyClass obj = new MyClass();

        // 验证
        assertEquals("mocked", obj.getValue());
        PowerMockito.verifyNew(MyClass.class).withNoArguments();
    }
}

Mock System 类(特殊)

时间相关的测试常用 PowerMock:

java
@RunWith(PowerMockRunner.class)
@PrepareForTest({System.class})
class SystemTimeTest {

    @Test
    void testWithFixedTime() {
        // 让 System.currentTimeMillis() 永远返回固定值
        PowerMockito.mockStatic(System.class);
        when(System.currentTimeMillis()).thenReturn(0L);

        // 被测代码调用 System.currentTimeMillis() 得到 0
        long now = System.currentTimeMillis();
        assertEquals(0L, now);
    }
}

PowerMock 的代价

问题影响
PowerMock 通过字节码修改实现,启动和执行都慢
JDK 版本JDK 9+ 需要额外配置
侵入性测试和被测代码耦合太紧
维护成本版本更新时容易出现兼容性问题

替代方案

原本需要 PowerMock 的场景更好的替代
Mock 静态工具类用依赖注入,让调用方传入 Mock 对象
Mock System 时间java.time.ClockMockito.mockStatic()(JDK 8+)
Mock 私有方法重新设计,让它变成可测试的公共方法

总结

不到万不得已,别用 PowerMock。先想想能不能通过重构代码来避免它。

PowerMock 是药三分毒,能用 Mockito 搞定的,坚决不用。

基于 VitePress 构建