PowerMockito verifyStatic:验证对静态方法的异步调用

问题描述 投票:0回答:1

我正在通过

static
方法调用外部系统

MyExternalServiceAccessor.myMethod(param1, param2);

到目前为止,我已经使用 PowerMockito

verifyStatic
 进行了 
单元测试,如下

import static org.mockito.Matchers.eq;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
..

mockStatic(MyExternalServiceAccessor.class);

..

verifyStatic();
MyExternalService.myMethod(eq(arg1), eq(arg2));

我现在想要进行上述调用“异步”(best-effort使用fire-and-forget语义进行调用,我不关心响应);所以参考这里我把它包裹在

CompletableFuture.runAsync(..)
里面,如下

import java.util.concurrent.CompletableFuture;

CompletableFuture.runAsync(() -> {
    MyExternalServiceAccessor.myMethod(param1, param2);
});

但是按照预期进行此更改后,单元测试变得不稳定,因为由于

MyExternalServiceAccessor.myMethod(..)
的异步调用,有时验证会失败并出现以下错误

java.lang.RuntimeException: Wanted but not invoked com.company.team.service.serviceutils.MyClass.myFunctionBeingUnitTested(
    null,
    null
);
Actually, there were zero interactions with this mock.

  • 我知道
    Mockito
    支持超时验证对于
    static
    方法;是否有与
    PowerMockito
    方法相同的
    Mockito
    (或
    static
    )等效项?
  • 如果没有,那么有什么替代方案?我可以巧妙地重写我的代码和测试,使其保持异步,同时仍然让单元测试每次都通过吗?
java unit-testing mockito completable-future powermockito
1个回答
0
投票

认为我可能已经通过使用在我的静态方法调用之后进行的非静态方法调用作为“代理”找到了修复,以暗示我们的静态方法调用也一定已经发生了

我正在记录错误指标,以防我的静态方法调用失败,如下所示

import java.util.concurrent.CompletableFuture;

CompletableFuture.runAsync(() -> {
    try {
        MyExternalServiceAccessor.myMethod(param1, param2);
    } catch (final Exception e) {
        // here metricLogger is injected into MyClass from outside so can be mocked for tests
        metricLogger.logCounter("MyExternalServiceAccessor.myMethod:failure", 1);
    }
});

现在在上面的代码中

  • metricLogger.logCounter(..)
    调用是一个非静态调用,我们可以利用mockito的超时验证
  • 并且由于它发生在 之后 我们的静态
    MyExternalServiceAccessor.myMethod(..)
    调用,因此我们可以确定,如果
    logCounter
    调用发生了,那么
    myMethod
    调用也一定发生了
    • 虽然上述陈述仅当我们对错误场景进行单元测试时才正确,其中
      myMethod
      抛出异常

因此,对于错误场景,我们会像这样编写测试,等待 100 毫秒,然后再验证调用的预期方法

verify(mockMetricLogger, timeout(100).times(1)).logCounter(eq("MyExternalServiceAccessor.myMethod:failure"), eq(1));
verifyStatic();
MyExternalService.myMethod(eq(arg1), eq(arg2));

对于非错误场景,我们可以使用这个线程中建议的技巧来等待并验证我们的模拟被称为“零”次,即,即使在等待后也没有被调用

verify(mockMetricLogger, timeout(100).times(0)).logCounter(eq("MyExternalServiceAccessor.myMethod:failure"), eq(1));
verifyStatic();
MyExternalService.myMethod(eq(arg1), eq(arg2));

到目前为止,我的测试已经通过,但由于(a)它们一开始并不太不稳定,(b)我们正在处理可能间歇性出现的臭名昭著的异步问题,我不确定是否没有错误片状现象是否已完全消除的情况。

(我当然可以完全消除不稳定的情况,例如在成功场景中发布一个计数器指标并将其用作代理,但现在还不想这样做)

更新1

我还必须测试整个代码块从未被调用的场景(由于上游条件表达式阻止流程到达触发异步块的这一点)

为此,我按照

此线程
中的建议利用了Mockito.never()

verify(mockMetricLogger, timeout(100).times(0)).logCounter(eq("MyExternalServiceAccessor.myMethod:failure"), eq(1));
verifyStatic(never());
MyExternalService.myMethod(eq(arg1), eq(arg2));
© www.soinside.com 2019 - 2024. All rights reserved.