1. 概述

单元测试中使用Mock框架已经被广泛认为是一种有效的做法,尤其是近年来,Mockito框架在这个领域占据了主导地位。

为了保持代码设计的优雅和接口的简洁性,某些功能被有意省略。导致了某些情况下,测试人员不得不写出繁琐的代码来创建模拟对象。这时候,PowerMock框架可以大显身手。

PowerMockito 是一个PowerMock插件,用于支持Mockito,弥补 Mockito 中缺失的功能,例如缺少模拟final、静态或私有方法的能力。

2. Maven 依赖

集成 PowerMock 对 Mockito 的支持,需要添加下面2个依赖

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.6.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.6.4</version>
    <scope>test</scope>
</dependency>

使用 PowerMockito 需要添加下面2个注解:

@RunWith(PowerMockRunner.class)
@PrepareForTest(fullyQualifiedNames = "com.baeldung.powermockito.introduction.*")

@PrepareForTest 注解告诉 PowerMockito 准备对 com.baeldung.powermockito.introduction 包中的所有类型以进行模拟。

现在我们准备好利用 PowerMockito 的强大功能了。

3. mock构造函数和final方法

在本节中,我们将演示在使用 new 实例化类时获取模拟实例而不是真实实例的方法,然后使用该对象来模拟final方法。

下面定义一个 CollaboratorWithFinalMethods 类,我们将mock其构造函数和 final 方法:

public class CollaboratorWithFinalMethods {
    public final String helloMethod() {
        return "Hello World!";
    }
}

首先,我们使用 PowerMockito 创建一个mock对象:

CollaboratorWithFinalMethods mock = mock(CollaboratorWithFinalMethods.class);

接下来,我们设置一个期望,即每当调用该类的无参数构造函数时,都应该返回一个模拟实例而不是真实的实例:

whenNew(CollaboratorWithFinalMethods.class).withNoArguments().thenReturn(mock);

让我们通过使用其默认构造函数实例化 CollaboratorWithFinalMethods 类来看看此构造模拟如何实际工作,然后我们将验证 PowerMock 的行为:

CollaboratorWithFinalMethods collaborator = new CollaboratorWithFinalMethods();
verifyNew(CollaboratorWithFinalMethods.class).withNoArguments();

下面,对final方法设置期望:

when(collaborator.helloMethod()).thenReturn("Hello Baeldung!");

然后执行该方法:

String welcome = collaborator.helloMethod();

以下断言确认已在 collaborator 对象上调用 helloMethod 方法并返回模拟期望设置的值:

Mockito.verify(collaborator).helloMethod();
assertEquals("Hello Baeldung!", welcome);

如果我们只想mock其中一个final方法而不是对象内的所有final方法,则 Mockito.spy(T object) 方法可能会派上用场。第 5 节对此进行了说明。

4. mock静态方法

假设我们想要mock CollaboratorWithStaticMethods 类的静态方法,其定义如下:

public class CollaboratorWithStaticMethods {
    public static String firstMethod(String name) {
        return "Hello " + name + " !";
    }

    public static String secondMethod() {
        return "Hello no one!";
    }

    public static String thirdMethod() {
        return "Hello no one again!";
    }
}

为了mock这些静态方法,我们需要使用 PowerMockito 注册封闭类:

mockStatic(CollaboratorWithStaticMethods.class);

或者,我们可以使用 Mockito.spy(Class class) 方法来mock指定方法,如下一节所示。

接下来,可以设置期望来定义方法在调用时应返回的值:

when(CollaboratorWithStaticMethods.firstMethod(Mockito.anyString()))
  .thenReturn("Hello Baeldung!");
when(CollaboratorWithStaticMethods.secondMethod()).thenReturn("Nothing special");

或者可以设置在调用 thirdMethod 方法时抛出异常:

doThrow(new RuntimeException()).when(CollaboratorWithStaticMethods.class);
CollaboratorWithStaticMethods.thirdMethod();

运行:

String firstWelcome = CollaboratorWithStaticMethods.firstMethod("Whoever");
String secondWelcome = CollaboratorWithStaticMethods.firstMethod("Whatever");

上述调用不是调用真实类的成员,而是委托给mock的方法。

验证:

assertEquals("Hello Baeldung!", firstWelcome);
assertEquals("Hello Baeldung!", secondWelcome);

我们还能够验证mock方法的行为,包括方法调用的次数。

// *firstMethod* 已被调用两次
verifyStatic(Mockito.times(2));
CollaboratorWithStaticMethods.firstMethod(Mockito.anyString());

// *secondMethod* 从未被调用        
verifyStatic(Mockito.never());
CollaboratorWithStaticMethods.secondMethod();

注意: 必须在任何静态方法验证之前调用 verifyStatic 方法,以便 PowerMockito 知道后续的方法调用是需要验证的。

最后,静态 ThirdMethod 方法应该抛出 RuntimeException ,如之前在mock中声明的那样:

@Test(expected = RuntimeException.class)
public void givenStaticMethods_whenUsingPowerMockito_thenCorrect() {
    // other methods   
       
    CollaboratorWithStaticMethods.thirdMethod();
}

5. 部分Mock

PowerMockito 允许使用 spy 方法mock部分类,而不是mock整个类。

CollaboratorForPartialMocking 定义如下:

public class CollaboratorForPartialMocking {
    public static String staticMethod() {
        return "Hello Baeldung!";
    }

    public final String finalMethod() {
        return "Hello Baeldung!";
    }

    private String privateMethod() {
        return "Hello Baeldung!";
    }

    public String privateMethodCaller() {
        return privateMethod() + " Welcome to the Java world.";
    }
}

让我们从模拟一个静态方法开始,该方法在上面的类定义中被命名为 staticMethod

首先,我们使用 PowerMockito 部分mock CollaboratorForPartialMocking 类并为其静态方法设置期望:

spy(CollaboratorForPartialMocking.class);
when(CollaboratorForPartialMocking.staticMethod()).thenReturn("I am a static mock method.");

然后执行静态方法:

returnValue = CollaboratorForPartialMocking.staticMethod();

模拟行为已验证:

verifyStatic();
CollaboratorForPartialMocking.staticMethod();

以下断言通过将返回值与期望值进行比较来确认模拟方法实际上已被调用:

assertEquals("I am a static mock method.", returnValue);

下面演示 final 和 private 方法。

CollaboratorForPartialMocking collaborator = new CollaboratorForPartialMocking();
CollaboratorForPartialMocking mock = spy(collaborator);

我们现在将通过设置期望并调用该方法来处理final方法:

when(mock.finalMethod()).thenReturn("I am a final mock method.");
returnValue = mock.finalMethod();

部分模拟该方法的行为已得到证明:

Mockito.verify(mock).finalMethod();

测试验证调用 FinalMethod 方法将返回与期望匹配的值:

assertEquals("I am a final mock method.", returnValue);

private方法类似。主要区别在于我们不能直接从测试用例中调用此方法。

基本上,私有方法由同一类中的其他方法调用。在 CollaboratorForPartialMocking 类中, privateMethod 方法由 privateMethodCaller 方法调用,我们将使用后者作为委托。

让我们从期望和调用开始:

when(mock, "privateMethod").thenReturn("I am a private mock method.");
returnValue = mock.privateMethodCaller();
verifyPrivate(mock).invoke("privateMethod");

以下测试确保私有方法调用的返回值与预期相同:

assertEquals("I am a private mock method. Welcome to the Java world.", returnValue);

6. 总结

本文介绍了 PowerMockito API,演示了它如何解决开发人员在使用 Mockito 框架时遇到的一些问题。

这些示例和代码片段的实现可以在链接的 GitHub 项目中找到。


« 上一篇: Java周报,122期
» 下一篇: Java周报,123期