使用 PowerMock 进行单元测试

单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。如果我们写的代码依赖于某些模块对象,而单元测试过程中这些对象又很难手动创建,或者模块还没有开发完成,那么就使用一个虚拟的对象来完成单元测试,这就是所谓的 Mock。

Java 单元测试中比较流行的 Mock 测试框架有 jMock EasyMock Mockito ,但是这些 Mock 工具都不能 Mock staticfinalprivate 方法等,而 PowerMock 能够做到。

使用 PowerMock,首先需要使用 @RunWith(PowerMockRunner.class) 将测试用例的 Runner 改为 PowerMockRunner。如果要 Mock staticfinalprivate 等方法的时候,就需要加注解 @PrepareForTest

PowerMock 有两个版本,一个是基于 EasyMock 实现的,另一个是基于 Mockito 实现的。

下面我将以 PowerMock 的 Mockito 的版本来讲述如何使用 PowerMock。

1. 普通 Mock(Mock 参数传递的对象)

测试对象

public class ClassUnderTest {
    public boolean callArgumentInstance(File file) {
        return file.exists();
    }
}

测试用例

public class TestClassUnderTest {
    @Test
    public void testCallArgumentInstance() {
        // Mock 对象,也可以使用 org.mockito.Mock 注解标记来实现
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();

        // 录制 Mock 对象行为
        PowerMockito.when(file.exists()).thenReturn(true);

        // 验证方法行为
        Assert.assertTrue(underTest.callArgumentInstance(file));
    }
}

普通 Mock 不需要加 @RunWith@PrepareForTest 注解。

2. Mock 方法内部 new 出来的对象

测试对象

public class ClassUnderTest {
    public boolean callInternalInstance(String path) {
       File file = new File(path);
       return file.exists();
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    // 在测试方法之上需要添加注解 @PrepareForTest,注解里写的类是需要 Mock 的 new 对象代码所在的类。
    @PrepareForTest(ClassUnderTest.class)
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();

        // 当以参数为 bbb 创建 File 对象的时候,返回已经 Mock 的 File 对象。
        PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);

        Assert.assertTrue(underTest.callInternalInstance("bbb"));
    }
}

3. Mock 普通对象的 final 方法

测试对象

public class ClassUnderTest {
    public boolean callFinalMethod(ClassDependency refer) {
        return refer.isAlive();
    }
}
public class ClassDependency {
    public final boolean isAlive() {
        // do something
        return false;
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    // 在测试方法之上加注解 @PrepareForTest,注解里写的类是需要 Mock 的 final 方法所在的类。
    @PrepareForTest(ClassDependency.class)
    public void testCallFinalMethod() {
        ClassDependency depencency = PowerMockito.mock(ClassDependency.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.when(depencency.isAlive()).thenReturn(true);

        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }
}

4. Mock 静态方法。

测试对象

public class ClassUnderTest {
    public boolean callStaticMethod() {
        return ClassDependency.isExist();
    }
}
public class ClassDependency {
    public static boolean isExist() {
        // do something
        return false;
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    // 在测试方法之上加注解 @PrepareForTest,注解里写的类是需要 Mock 的 static 方法所在的类。
    @PrepareForTest(ClassDependency.class)
    public void testCallStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();

        // 表示需要 Mock 这个类里的静态方法
        PowerMockito.mockStatic(ClassDependency.class);
        PowerMockito.when(ClassDependency.isExist()).thenReturn(true);

        Assert.assertTrue(underTest.callStaticMethod());
    }
}

5. Mock 私有方法

测试对象

public class ClassUnderTest {

    public boolean callPrivateMethod() {
        return isExist();
    }

    private boolean isExist() {
        return false;
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    // 在测试方法之上加注解 @PrepareForTest,注解里写的类是需要 Mock 的 private 方法所在的类。
    @PrepareForTest(ClassUnderTest.class)
    public void testCallPrivateMethod() throws Exception {
        ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);

        PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod();
        PowerMockito.when(underTest, "isExist").thenReturn(true);

        Assert.assertTrue(underTest.callPrivateMethod());
    }
}

6. Mock JDK 中类的静态、私有方法。

测试对象

public class ClassUnderTest {

    public boolean callSystemFinalMethod(String str) {
        return str.isEmpty();
    }

    public String callSystemStaticMethod(String str) {
        return System.getProperty(str);
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest
    @Test
    // 和 Mock 普通对象的 static、final 方法一样,只不过注解 @PrepareForTest 里写的类不一样
  	// 注解里写的类是需要调用系统方法所在的类。
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemFinalMethod() {
        String str = PowerMockito.mock(String.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.when(str.isEmpty()).thenReturn(false);

        Assert.assertFalse(underTest.callSystemFinalMethod(str));
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");

        Assert.assertEquals("bbb", underTest.callSystemStaticMethod("aaa"));
    }
}

7. Mock 依赖类中的方法(whenNew)

测试对象

public class ClassUnderTest {

    public boolean callDependency() {
        ClassDependency classDependency = new ClassDependency();
        return classDependency.isGod("hh");
    }
}
public class ClassDependency {
    public boolean isGod(String oh){
        System.out.println(oh);
        return false;
    }
}

测试用例

// 必须加注解 @PrepareForTest 和 @RunWith
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
  	// 注解里写的类是依赖类所在的类。
    @PrepareForTest(ClassUnderTest.class)
    public void testDependency() throws Exception {
        ClassUnderTest underTest = new ClassUnderTest();
        ClassDependency dependency = mock(ClassDependency.class);

        whenNew(ClassDependency.class).withAnyArguments().thenReturn(dependency);

        when(dependency.isGod(anyString())).thenReturn(true);
        Assert.assertTrue(underTest.callDependency());
    }
}

8. 完整示例代码

测试目标类

package cn.enncloud.ceres.powermock;

import java.io.File;

/**
 * Created by lixiangrong on 2017/7/21.
 */
public class ClassUnderTest {

    public boolean callArgumentInstance(File file) {
        return file.exists();
    }

    public boolean callInternalInstance(String path) {
        File file = new File(path);
        return file.exists();
    }

    public boolean callFinalMethod(ClassDependency refer) {
        return refer.isAlive();
    }

    public boolean callSystemFinalMethod(String str) {
        return str.isEmpty();
    }

    public boolean callStaticMethod() {
        return ClassDependency.isExist();
    }

    public String callSystemStaticMethod(String str) {
        return System.getProperty(str);
    }

    public boolean callPrivateMethod() {
        return isExist();
    }

    public boolean callVoidPrivateMethod(){
        testVoid();
        return true;
    }

    private boolean isExist() {
        // do something
        return false;
    }

    private void testVoid(){
        System.out.println("do nothing");
    }

    public boolean callDependency() {
        ClassDependency classDependency = new ClassDependency();
        return classDependency.isGod("hh");
    }
}

依赖类

package cn.enncloud.ceres.powermock;

/**
 * Created by lixiangrong on 2017/7/21.
 */
public class ClassDependency {

    public static boolean isExist() {
        // do something
        return false;
    }

    public final boolean isAlive() {
        // do something
        return false;
    }

    public boolean isGod(String oh){
        System.out.println(oh);
        return false;
    }
}

测试用例

package cn.enncloud.ceres.powermock.test;

import cn.enncloud.ceres.powermock.ClassDependency;
import cn.enncloud.ceres.powermock.ClassUnderTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;

/**
 * Created by lixiangrong on 2017/7/21.
 */
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    public void testCallArgumentInstance() {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.when(file.exists()).thenReturn(true);

        Assert.assertTrue(underTest.callArgumentInstance(file));
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);

        Assert.assertTrue(underTest.callInternalInstance("bbb"));
    }

    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallFinalMethod() {
        ClassDependency depencency = PowerMockito.mock(ClassDependency.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.when(depencency.isAlive()).thenReturn(true);

        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemFinalMethod() {
        String str = PowerMockito.mock(String.class);
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.when(str.isEmpty()).thenReturn(false);

        Assert.assertFalse(underTest.callSystemFinalMethod(str));
    }

    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.mockStatic(ClassDependency.class);
        PowerMockito.when(ClassDependency.isExist()).thenReturn(true);

        Assert.assertTrue(underTest.callStaticMethod());
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();

        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");

        Assert.assertEquals("bbb", underTest.callSystemStaticMethod("aaa"));
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallPrivateMethod() throws Exception {
        ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);

        PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod();
        PowerMockito.when(underTest, "isExist").thenReturn(true);

        Assert.assertTrue(underTest.callPrivateMethod());
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallVoidPrivateMethod() throws Exception {
        ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);

        PowerMockito.when(underTest.callVoidPrivateMethod()).thenCallRealMethod();
        PowerMockito.doNothing().when(underTest, "testVoid");

        Assert.assertTrue(underTest.callVoidPrivateMethod());
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testDependency() throws Exception {
        ClassUnderTest underTest = new ClassUnderTest();
        ClassDependency dependency = mock(ClassDependency.class);

        // @PrepareForTest(ClassUnderTest.class)
        whenNew(ClassDependency.class).withAnyArguments().thenReturn(dependency);

        when(dependency.isGod(anyString())).thenReturn(true);
        Assert.assertTrue(underTest.callDependency());
    }
}

9. Mock 与 Spy

Mock 不是真实的对象,它只是用类型的 class 创建了一个虚拟对象,并可以设置对象行为 Spy 是一个真实的对象,但它可以设置对象行为