内部有 CompletableFuture 的方法的单元测试

Unit test of method with CompletableFuture inside

我有一个方法,它以异步方式调用 connector.runSomeService(data) 并处理方法 handleServiceResponse(res, node) 中的响应。

public void runServiceOnAllNodes(Collection<Node> nodes, Object data) {
    nodes.parallelStream().forEach(node -> {
        CompletableFuture<ResponseEntity> response = CompletableFuture
                .supplyAsync(()-> connector.runSomeService(data));
        response.exceptionally(ex -> {
                    log.error("OMG...OMG!!!")
                    return null;
                })
                .thenAcceptAsync(res -> handleServiceResponse(res, node));
    });
}


private void handleServiceResponse(ResponseEntity res, Node node) {
    if (res.isOK) {
        node.setOKStatus();
    } else {
        node.setFailStatus();
    }
    dbService.saveNode(node);
}

尝试创建单元测试,但当我尝试验证响应是否得到正确处理时,UT 的结果是不确定的。

@Test
public void testRunServiceOnAllNodes() {
    // given
    List<Collector> nodes = Arrays.asList(node1, node2, node3);
    when(connector.runSomeService(eq(node1), eq(data))).thenReturn(ResponseEntity.ok().body("{message:OK}"));
    when(connector.runSomeService(eq(node2), eq(data))).thenReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""));
    when(connector.runSomeService(eq(node3), eq(data))).thenThrow(new ResourceAccessException(""));

    // when
    engine.runServiceOnAllNodes(data, collectors);

    // then
    verify(connector, times(1)).runSomeService(eq(node1), eq(data));
    verify(connector, times(1)).runSomeService(eq(node2), eq(data));
    verify(connector, times(1)).runSomeService(eq(node3), eq(data));
    verifyNoMoreInteractions(connector);
    assertEquals(node1.getStatus(), "OK");
    assertEquals(node2.getStatus(), "Fail");
}

它可以以一些不同的结果结束,例如。

Wanted but not invoked:
connector.runSomeService(node2);

However, there were other interactions with this mock:
connector.runSomeService(node1);

Argument(s) are different! Wanted:
connector.runSomeService(node1);

Actual invocation has different arguments:
connector.deployFileset(node2);

或者有时它以成功结束。

很明显执行的时间connector.runSomeService()和验证的时间可以交错。这两个动作的顺序是不确定的。

使用睡眠很糟糕。试图收集所有响应并调用 future.get()

// when
engine.runServiceOnAllNodes(data, collectors);
for (CompletableFuture future : engine.getResponses()) {
    future.get();
}

但我遇到了一些异常,但我仍然觉得这种方式也很糟糕,不是吗?

我建议将 runServiceOnAllNodes 方法更改为 return a Future 这样您的测试以及作为奖励的普通客户端都可以显式等待异步行为完成。

public Future<Void> runServiceOnAllNodes(Collection<Node> nodes, Object data) {
    return nodes.parallelStream().map(node -> {
        CompletableFuture<ResponseEntity> response = CompletableFuture
                .supplyAsync(()-> connector.runSomeService(data));
        return response.exceptionally(ex -> {
            LOGGER.error("OMG...OMG!!!");
            return null;
        })
        .thenAcceptAsync(res -> handleServiceResponse(res, node));
    })
    .reduce(CompletableFuture::allOf).orElseGet(() -> CompletableFuture.completedFuture(null));
}

在您的测试中,这只是在进行断言和验证之前对未来调用 get() 的简单问题。