PowerMock——增强版mockito实现静态或私有方法的mock

4,326 阅读5分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

1. 概述

长期以来,借助 Mocking框架进行单元测试一直被认为是一种有用的实践,尤其是Mockito框架在近年来占据了主导地位。

并且为了促进体面的代码设计并使公共 API 变得简单,一些需要的特性被有意地遗漏了。然而,在某些情况下,这些缺点迫使测试人员编写繁琐的代码只是为了使模拟的创建可行。

这就是PowerMock框架发挥作用的地方。

PowerMockito是 PowerMock 的扩展 API,用于支持 Mockito。它以一种简单的方式提供了使用 Java 反射 API 的功能,以克服 Mockito 的问题,例如缺乏模拟final、静态或私有方法的能力。

本文将介绍 PowerMockito API 及其在测试中的应用。

2.准备

为 Mockito 集成 PowerMock 支持的第一步是在 Maven POM 文件中包含以下两个依赖项:

<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的测试用例:

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

@PrepareForTest注释中的fullyQualifiedNames元素表示我们想要模拟的类型的完全限定名称数组。在这种情况下,我们使用带有通配符的包名称来告诉PowerMockito准备com.baeldung.powermockito.introduction包中的所有类型以进行mock

3. 模拟构造函数和最终方法

在本节中,我们将演示在使用new运算符实例化类时获取模拟实例而不是真实实例的方法,然后使用该对象模拟最终方法。协作类的构造函数和最终方法将被模拟,定义如下:

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

首先,我们使用PowerMockito API创建一个模拟对象:

CollaboratorWithFinalMethods mock = mock(CollaboratorWithFinalMethods.class);

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

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

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

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

在下一步中,将期望设置为最终方法:

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

然后执行此方法:

String welcome = collaborator.helloMethod();

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

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

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

4.模拟静态方法

假设我们要模拟名为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!";
    }
}

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

mockStatic(CollaboratorWithStaticMethods.class);

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

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

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");

上面的调用不是调用真实类的成员,而是委托给模拟的方法。以下断言证明模拟已经生效:

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

我们还能够验证模拟方法的行为,包括调用方法的次数。在这种情况下,firstMethod已被调用两次,而secondMethod从未被调用:

verifyStatic(Mockito.times(2));
CollaboratorWithStaticMethods.firstMethod(Mockito.anyString());
        
verifyStatic(Mockito.never());
CollaboratorWithStaticMethods.secondMethod();

注:verifyStatic必须调用方法正确之前,任何静态方法验证PowerMockito知道连续方法调用是需要验证的内容。

最后,静态的thirdMethod方法应该抛出一个RuntimeException, 就像之前在模拟上声明的那样。它由 @Test注释的预期元素验证:

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

5. 部分模拟

PowerMockito API 允许使用spy方法模拟其中的一部分,而不是模拟整个类。以下类将用作协作者来说明 PowerMock 对部分模拟的支持:

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 API 部分模拟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 和私有方法。为了说明这些方法的部分模拟,我们需要实例化该类并告诉PowerMockito API 来监视它:

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

类似的过程适用于私有方法。主要区别在于我们不能直接从测试用例中调用这个方法。基本上,私有方法将被来自同一类的其他方法调用。在CollaboratorForPartialMocking类中,privateMethod方法将由privateMethodCaller方法调用,我们将使用后者作为委托。让我们从期望和调用开始:

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

私有方法的mock得到确认:

verifyPrivate(mock).invoke("privateMethod");

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

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

六,小结

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