【问题标题】:Mockito Test case for CompletableFuture.runAsync() which calls void method调用 void 方法的 CompletableFuture.runAsync() 的 Mockito 测试用例
【发布时间】:2019-08-18 20:17:36
【问题描述】:

我需要帮助为以下方法编写模拟测试用例。

public void getCouponAndNotifyAsync(String countryId, String channelId,
        String storeNumber, String clientId, NotificationRequest notificationRequest)
        throws FirestoreException, TurneroServiceException {
    CompletableFuture.runAsync(() -> getCouponAndNotify(countryId, channelId,
            storeNumber, clientId, notificationRequest));
}

其中 getCouponAndNotify() 是一个 void 方法。

在下面尝试过,但它不起作用

@Test
    public void getCouponAndNotifyAsync() throws Exception {
        //doNothing().when(turneroService).getCouponAndNotify(COUNTRYID, CHANNELID, STORENUMBER, CLIENTID, new NotificationRequest("ext_rborse@falabella.cl", "all"));

        CompletableFuture<Void> runAsync = CompletableFuture
                .runAsync(() -> doNothing().when(turneroService).getCouponAndNotify(COUNTRYID, CHANNELID, STORENUMBER, CLIENTID, new NotificationRequest("ext_rborse@falabella.cl", "all")));

        assertTrue(runAsync.isDone());

    }

更新了测试用例,但仍然无法正常工作。

@Test
    public void getCouponAndNotifyAsync() throws Exception {
        //doNothing().when(turneroService).getCouponAndNotify(COUNTRYID, CHANNELID, STORENUMBER, CLIENTID, new NotificationRequest("ext_rborse@falabella.cl", "all"));

        CompletableFuture<Void> runAsync = CompletableFuture
                .runAsync(() -> doNothing().when(turneroService).getCouponAndNotify(COUNTRYID, CHANNELID, STORENUMBER, CLIENTID, new NotificationRequest("ext_rborse@falabella.cl", "all")));

        assertTrue(ForkJoinPool.commonPool().awaitQuiescence(5, TimeUnit.SECONDS));
        assertTrue(runAsync.isDone());

    }

【问题讨论】:

  • 您是否尝试使用partial mock?您最好在测试中扩展类并覆盖同步方法。
  • 你在哪里打电话getCouponAndNotifyAsync()在你的测试?我也不认为 mock 不像你想象的那样工作。
  • 你能解释一下吗? getCouponAndNotifyAsync() 在控制器类中被调用。该测试通过了。

标签: java spring-boot junit java-8 mockito


【解决方案1】:

要验证您的异步方法是否被调用,您可以使用

 verify(<<your service>>, **timeout(100)**.times(1))
      .<<your method>>(<< arguments>>);

例如:-

verify(service, **timeout(100)**.times(1))
      .getCouponAndNotify(anyString(), anyString(), anyString(), anyString(), any(NotificationRequest.class));

参考https://www.javadoc.io/doc/org.mockito/mockito-core/2.2.9/org/mockito/verification/VerificationWithTimeout.html

【讨论】:

    【解决方案2】:

    假设你有这个代码:

    public void method() {
            CompletableFuture.runAsync(() -> {
                //logic
                //logic
                //logic
                //logic
            });
        }
    

    尝试将其重构为这样的:

    public void refactoredMethod() {
        CompletableFuture.runAsync(this::subMethod);
    }
    
    private void subMethod() {
        //logic
        //logic
        //logic
        //logic
    }
    

    之后,以这种方式测试 subMethod:

    org.powermock.reflect.Whitebox.invokeMethod(classInstance, "subMethod"); 
    Mockito.verify(...)
    

    这不是一个完美的解决方案,但它会测试异步执行中的所有逻辑。

    【讨论】:

      【解决方案3】:

      我假设您正在其他地方测试 getCouponAndNotify(),因此您不必担心它会引发异常。

      您将遇到getCouponAndNotifyAsync()getCouponAndNotify() 返回之间的竞争条件。有几个解决方案:

      既然你用的是普通的ForkJoinPool,那么做

      assertTrue(ForkJoinPool.commonPool().awaitQuiescence(5, TimeUnit.Seconds));
      

      It waits for the task to finish.

      或者,您可以注入ExecutorService 并将其用作supplyAsync() 的第二个参数。您有几个选择:您可以使用模拟,可以使用 ExecutorServiceruns with the current thread,或者您可以注入标准 Executors.newSingleThreadExecutor(),然后在测试中调用 shutdown()awaitTermination()

      您还可以从getCouponAndNotifyAsync() 返回一个CompletionStage&lt;Void&gt;,您可以在测试中等待。

      【讨论】:

      • 在我的测试用例中,在 assertTrue 之前写的任何内容都可以吗?
      • 更新了有问题的测试用例,但仍然无法正常工作
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      相关资源
      最近更新 更多