简体   繁体   中英

Unit test of method with CompletableFuture inside

I have method which in async way calls connector.runSomeService(data) and handles the response in method 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);
}

Try to create unit test but when I try to verify if response is properly handled, the result of UT is non deterministic.

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

It can end with a few different results eg.

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

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

or

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

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

or sometimes it ends with success.

It is clear that the time of execution connector.runSomeService() and the time of the verification can interlace. The order of this two actions is not deterministic.

Using sleep sucks. Tried to gather all responses and calling future.get()

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

but I'm getting some exception but I still have the feeling that this way also sucks, isn't it?

I would suggest changing the runServiceOnAllNodes method to return a Future so your test, and, as a bonus, normal clients as well, can explicitly wait for the async behavior to finish.

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

In your test, it is then simply a matter of calling get() on the future prior to making assertions and verifications.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM