1. 概述

单元测试中的一个挑战是mock private方法。本文我们将学习如何使用PowerMock库来实现这一目标,它得到了JUnit和TestNG的支持。

PowerMock与EasyMock和Mockito等mock框架集成,并为其增加了额外功能,如模拟private方法、final类和final方法等。

它通过依赖字节码操作和独立的ClassLoader来实现这一点。

2. Maven 依赖

首先,让我们在pom.xml中添加使用PowerMock与Mockito和JUnit的所需依赖:

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.7.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

最新的版本可以在这里这里查看。

3. 示例

假设我们有个 LuckyNumberGenerator 类,它有一个生成幸运数字的public方法:

public int getLuckyNumber(String name) {
    saveIntoDatabase(name);
    if (name == null) {
        return getDefaultLuckyNumber();
    }
    return getComputedLuckyNumber(name.length());
}

其中 getDefaultLuckyNumbergetComputedLuckyNumbersaveIntoDatabase 是下面我们将要 mock 的 private 方法。

4. Mock 私有方法

4.1. Mock无参数但有返回值的方法

首先对 getDefaultLuckyNumber 方法进行 mock,这是一个无参私有方法。我们使其返回值为 300:

LuckyNumberGenerator mock = spy(new LuckyNumberGenerator());

when(mock, "getDefaultLuckyNumber").thenReturn(300);

4.2. Mock有参数和返回值的方法

然后对 getComputedLuckyNumber 进行 mock,它带有一个参数:

LuckyNumberGenerator mock = spy(new LuckyNumberGenerator());

doReturn(1).when(mock, "getComputedLuckyNumber", ArgumentMatchers.anyInt());

这里,我们固定返回1。另外,我们并不关心输入参数,使用ArgumentMatchers.anyInt()作为通配符。

4.3. 验证方法调用

验证private方法是否被调用:

LuckyNumberGenerator mock = spy(new LuckyNumberGenerator());
int result = mock.getLuckyNumber("Tyranosorous");

verifyPrivate(mock).invoke("saveIntoDatabase", ArgumentMatchers.anyString());

5. 警告

最后,虽然可以使用PowerMock测试私有方法,但我们必须谨慎对待这种技术。

考虑到我们的测试目的是验证类的行为,我们应该避免在单元测试期间改变类的内部行为。

模拟技术应该应用于类的外部依赖,而不是类本身。

如果对私有方法的模拟对于测试我们的类至关重要,通常意味着设计不佳。

6. 总结

在这篇简短的文章中,我们展示了如何使用PowerMock扩展Mockito的功能,以在测试的类中模拟和验证私有方法。

这个教程的源代码可以在GitHub上找到