PowerMock 注解和使用方法

PowerMock 注解和使用方法

注解概述

PowerMock 有两个重要的注解:

  • @RunWith(PowerMockRunner.class)
  • @PrepareForTest({MyObject.class})

@PrepareForTest@RunWith 注解是结合使用的,不要单独使用它们中的任何一个,否则不起作用。当使用 PowerMock 去 mock 静态、final 或者私有方法时,需要加上这两个注解。

注意:在输入 @RunWith 注解时,IDE(如 Eclipse)可能会自动导入 org.powermock.modules.junit4.legacy.PowerMockRunner 包,记得把它换成 org.powermock.modules.junit4.PowerMockRunner,否则会抛出 java.lang.NoClassDefFoundError 异常。

普通 Mock

测试目标代码

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

测试用例代码

@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));
}

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

Mock 方法调用

测试目标代码

public String getFilePath() {
    return path;
}

public String getPayloadName() {
    String pathWithName = getFilePath();
    try {
        int index = pathWithName.lastIndexOf(Constant.SLASH);
        return pathWithName.substring(index + 1);
    } catch (Exception e) {
        return pathWithName;
    }
}

测试用例代码

@Test
public void testGetPayloadName() throws Exception {
    FileItem item = PowerMockito.mock(FileItem.class);
    String filePath = "../../../test/updates/Citrix.ibr";
    PowerMockito.when(item.getFilePath()).thenReturn(filePath);
    PowerMockito.when(item, "getPayloadName").thenCallRealMethod();
    assertEquals("Citrix.ibr", item.getPayloadName());
}

说明:当使用 mock 出来的对象调用某个方法时,要对该方法使用 thenCallRealMethod()

whenNew

测试目标代码

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

测试用例代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassUnderTest.class)
public class TestClassUnderTest {
    @Test
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.whenNew(File.class).withArguments("bing").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callInternalInstance("bing"));
    }
}

说明:当使用 PowerMockito.whenNew 方法时,必须加上 @PrepareForTest@RunWith 注解。注解 @PrepareForTest 里写的类是需要 mock 的 new 对象代码所在的类。

Mock final 方法

测试目标代码

public class ClassUnderTest {
    public boolean callFinalMethod(Dependency d) {
        return d.isAlive();
    }
}

class Dependency {
    public final boolean isAlive() {
        return true;
    }
}

测试用例代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(Dependency.class)
public class TestClassUnderTest {
    @Test
    public void testCallFinalMethod() {
        Dependency dependency = PowerMockito.mock(Dependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(dependency.isAlive()).thenReturn(true);
        Assert.assertTrue(underTest.callFinalMethod(dependency));
    }
}

说明:当需要 mock final 方法的时候,必须加上 @PrepareForTest@RunWith 注解,@PrepareForTest 里写的类是 final 方法所在的类。

Mock 私有方法

测试目标代码

public class ClassUnderTest {
    public boolean callPrivateMethod() {
        return isAlive();
    }

    private boolean isAlive() {
        return true;
    }
}

测试用例代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassUnderTest.class)
public class TestClassUnderTest {
    @Test
    public void testCallPrivateMethod() throws Exception {
        // 创建 ClassUnderTest 实例并使用 spy 包装它
        ClassUnderTest underTest = PowerMockito.spy(new ClassUnderTest());
        // 使用 PowerMockito.when() 和 doReturn() 来 mock 私有方法
        PowerMockito.doReturn(false).when(underTest, "isAlive");
        // 验证调用私有方法的结果
        assertFalse(underTest.callPrivateMethod());
    }
}

说明:与 mock final 方法一样,必须加上 @PrepareForTest@RunWith 注解,@PrepareForTest 里写的类是 private 方法所在的类。

Mock 静态方法

测试目标代码

public class ClassUnderTest {
    public boolean callStaticMethod(Dependency d) {
        return Dependency.isExist();
    }
}

class Dependency {
    public static boolean isExist() {
        return true;
    }
}

测试用例代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(Dependency.class)
public class TestClassUnderTest {
    @Test
    public void testCallStaticMethod() {
        PowerMockito.mockStatic(Dependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(Dependency.isExist()).thenReturn(true);
        Assert.assertTrue(underTest.callStaticMethod(depencency));
    }
}

说明:mock 静态方法时需要加上 @PrepareForTest@RunWith 注解,@PrepareForTest 注解中的类是静态方法所在的类。

suppress

测试目标代码

public static class Utility {
    /**
     * 一个模拟的耗时操作。
     *
     * @return 耗时操作的结果
     */
    public static String performExpensiveOperation() {
        // 假设这是一个很耗时的操作
        return "Expensive operation result";
    }

    public static String processData() {
        // 使用耗时操作
        String result = performExpensiveOperation();
        return "Processed: " + result;
    }
}

测试用例代码

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Utility.class })
public class TestRefreshMgmt {
/**
     * Suppress 示例
     */
    @Test
    public void testProcessDataWithSuppressedMethod() {
        // 抑制 Utility 类的静态方法 performExpensiveOperation
        PowerMockito.suppress(PowerMockito.method(Utility.class, "performExpensiveOperation"));
        // 实例化被测试的类
        Utility utility = new Utility();
        // 调用需要测试的方法
        String result = utility.processData();
        // 验证结果,这里因为 performExpensiveOperation 被抑制,result 将不包含真实结果
        assertEquals("Processed: null", result);
    }
}

说明:PowerMockito.suppress 方法用于禁用某个域或方法。在本例中,使用 PowerMockito.suppress() 来抑制 performExpensiveOperation() 方法的执行。这样在调用 processData() 时,这个耗时操作将不会执行,返回 null。

WhiteBox

测试目标代码

public class CataElement {
    private boolean isAvailable = false;
    private List<FileItem> items = new ArrayList<>();

    private Date parseDate(String date) {
        if (!isAvailable) return null;
        SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
        try {
            if (date == null || date.isEmpty()) return null;
            return sdf.parse(date);
        } catch (ParseException e) {
            return null;
        }
    }
}

测试用例代码

public class CataElementTest {
    @Test
    public void test() {
        CataElement e = new CataElement();
        Whitebox.setInternalState(e, "isAvailable", true);
        List<FileItem> items = Whitebox.getInternalState(e, "items");
        assertTrue(items.size() == 0);
    }
}

Whitebox 方法:

  • Whitebox.setInternalState(object, "fieldName", value):设置某个对象的某个字段的值。
  • Whitebox.getInternalState(object, "fieldName"):获取某个对象的某个字段的值。
  • Whitebox.invokeMethod(object, methodName, para):调用私有方法,测试私有方法的返回值。

Answer

测试目标代码

public class EmployeeService {
    public Employee findEmployeeByEmail(String email) {
        // 模拟的实际业务逻辑
        return null;
    }
}
public class Employee {
    private String name;
    private String email;

    public Employee() {}

    public Employee(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', email='" + email + "'}";
    }
}

测试用例代码

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.*;

@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class) // PowerMockito 需要准备被 mock 的类
public class EmployeeServiceTest {

    @Test
    public void testFindEmployeeByEmail() throws Exception {
        // 使用 PowerMockito mock EmployeeService 类
        EmployeeService mockEmployeeService = PowerMockito.mock(EmployeeService.class);

        // 使用 Answer 动态定义 findEmployeeByEmail 的行为
        PowerMockito.when(mockEmployeeService.findEmployeeByEmail(Matchers.anyString()))
            .thenAnswer(new Answer<Employee>() {
                @Override
                public Employee answer(InvocationOnMock invocation) throws Throwable {
                    // 获取传入的 email 参数
                    String email = (String) invocation.getArguments()[0];
                    
                    // 根据不同的 email 返回不同的 Employee 对象
                    if (email == null) {
                        return null;
                    } else if (email.startsWith("deep")) {
                        return new Employee("Deep Employee", email);
                    } else {
                        return new Employee("Generic Employee", email);
                    }
                }
            });

        // 验证模拟行为
        Employee deepEmployee = mockEmployeeService.findEmployeeByEmail("deep@test.com");
        assertNotNull(deepEmployee);
        assertEquals("Deep Employee", deepEmployee.getName());
        assertEquals("deep@test.com", deepEmployee.getEmail());

        Employee genericEmployee = mockEmployeeService.findEmployeeByEmail("generic@test.com");
        assertNotNull(genericEmployee);
        assertEquals("Generic Employee", genericEmployee.getName());
        assertEquals("generic@test.com", genericEmployee.getEmail());

        // 验证传入 null 时的行为
        Employee nullEmployee = mockEmployeeService.findEmployeeByEmail(null);
        assertNull(nullEmployee);
    }
}

说明:改测试方法根据不同的参数返回不同的结果,Answer的泛型类型

必须和answer方法的返回值类型一致。

Answer接口指定执行的action和返回值。Answer的参数是InvocationOnMock的实例,支持:
callRealMethod():调用真正的方法
getArguments():获取所有参数
getMethod():返回mock实例调用的方法
getMock():获取mock实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿界新星蔡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值