2017-07-04 1 views
0

J'ai une méthode qui de manière asynchrone appelle connector.runSomeService(data) et gère la réponse dans la méthode handleServiceResponse(res, node).Test unitaire de la méthode avec CompletableFuture à l'intérieur

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

Essayez de créer des tests unitaires, mais lorsque je tente de vérifier si la réponse est bien gérée, le résultat de l'UT est non déterministe.

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

Il peut se terminer par quelques résultats différents par exemple.

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

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

ou

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

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

ou parfois elle se termine avec le succès.

Il est clair que l'heure d'exécution connector.runSomeService() et l'heure de la vérification peuvent s'entrelacer. L'ordre de ces deux actions n'est pas déterministe.

L'utilisation de sleep sucks. Essayé de recueillir toutes les réponses et appelant Future.get()

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

mais je suis en train une exception, mais je dois encore le sentiment que cette façon suce aussi, est-ce pas?

Répondre

1

Je suggère de changer la méthode runServiceOnAllNodes pour retourner un Future ainsi votre test, et, en prime, les clients normaux aussi, peuvent explicitement attendre que le comportement asynchrone se termine.

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

Dans votre essai, il est alors simplement une question d'appeler get() sur l'avenir avant de faire des affirmations et des vérifications.