web-dev-qa-db-fra.com

CompletableFuture utilisabilité et test unitaire

J'apprends à propos de Java 8 CompletableFuture et j'ai fini avec cela.

Tout d'abord, que pensez-vous de ces lignes de code? Je dois envoyer une demande à différents services en parallèle, puis attendre que tous répondent et continuent à travailler.

//service A
CompletableFuture<ServiceAResponse> serviceAFuture = CompletableFuture.supplyAsync(
    () -> this.ServiceA.retrieve(serviceARequest), serviceAExecutorService
);

//service B
CompletableFuture<ServiceBResponse> serviceBFuture = CompletableFuture.supplyAsync(
    () -> this.ServiceB.retrieve(serviceBRequest), serviceBExecutorService
);

CompletableFuture.allOf(serviceAFuture, serviceBFuture).join();
ServiceAResponse responseA = serviceAFuture.join();
ServiceBResponse responseB = serviceBFuture.join();

Et même le code fait ce que je veux, j'ai du mal à tester la classe où se trouve ce code. J'ai essayé d'utiliser Mockito et faire quelque chose comme:

doAnswer(invocation -> CompletableFuture.completedFuture(this.serviceAResponse))
    .when(this.serviceAExecutorService)
    .execute(any());

Où les services de l'exécutant et les réponses des services se moquent, mais le test ne se termine jamais et le fil continue d'attendre quelque chose dans cette ligne

CompletableFuture.allOf(serviceAFuture, serviceBFuture).join();

Un indice sur ce qui me manque ici? Je vous remercie!

7
Leo

Si j'étais vous, je me moquerais simplement des services A et B et de vos exécuteurs, puis les injecterais grâce à l'annotation @InjectMocks car ils sont des champs de votre classe.

Si vous voulez simuler la méthode execute de votre Executor, vous devriez plutôt procéder comme suit pour simplement appeler la méthode run de la Runnable fournie:

doAnswer(
    (InvocationOnMock invocation) -> {
        ((Runnable) invocation.getArguments()[0]).run();
        return null;
    }
).when(serviceAExecutorService).execute(any(Runnable.class));

Donc, fondamentalement, votre test ressemblerait à ceci:

@RunWith(MockitoJUnitRunner.class)
public class CompletableFutureServiceTest {

    // The mock of my service A
    @Mock
    private ServiceA ServiceA;
    // The mock of my service B
    @Mock
    private ServiceB ServiceB;
    // The mock of your executor for the service A
    @Mock
    private Executor serviceAExecutorService;
    // The mock of your executor for the service B
    @Mock
    private Executor serviceBExecutorService;
    // My class in which I want to inject the mocks
    @InjectMocks
    private CompletableFutureService service;

    @Test
    public void testSomeMethod() {
        // Mock the method execute to call the run method of the provided Runnable
        doAnswer(
            (InvocationOnMock invocation) -> {
                ((Runnable) invocation.getArguments()[0]).run();
                return null;
            }
        ).when(serviceAExecutorService).execute(any(Runnable.class));
        doAnswer(
            (InvocationOnMock invocation) -> {
                ((Runnable) invocation.getArguments()[0]).run();
                return null;
            }
        ).when(serviceBExecutorService).execute(any(Runnable.class));

        ServiceAResponse serviceAResponse = ... // The answer to return by service A
        // Make the mock of my service A return my answer
        when(ServiceA.retrieve(any(ServiceARequest.class))).thenReturn(
            serviceAResponse
        );
        ServiceBResponse serviceBResponse = ... // The answer to return by service B
        // Make the mock of my service B return my answer
        when(ServiceB.retrieve(any(ServiceBRequest.class))).thenReturn(
            serviceBResponse
        );

        // Execute my method
        ServiceResponse response = service.someMethod(
            new ServiceARequest(), new ServiceBRequest()
        );

        // Test the result assuming that both responses are wrapped into a POJO
        Assert.assertEquals(serviceAResponse, response.getServiceAResponse());
        Assert.assertEquals(serviceBResponse, response.getServiceBResponse());
    }
}
7
Nicolas Filotto
  @Mock
  private AsyncExecuter asyncExecuter;
  @Mock
  private CompletableFuture<XyzSample> xyzSampleResponse;
  @Mock
  private CompletableFuture<Map<String, String>> abcSampleResponse;

 @Before
  public void setUp() throws Exception {

    abcSampleResponse = CompletableFuture.completedFuture(TestUtil.readJsonResource(
        "misc_mapper_response.json", new TypeReference<Map<String, String>>() {
        }));

    xyzSampleResponse = CompletableFuture.completedFuture(TestUtil.readJsonResource(
        "gp_facade_response.json", new TypeReference<XyzSample>() {
        }));

  }

   @Test
  public void testAbcMethod() {

    Mockito.doReturn(abcSampleResponse).when(asyncExecuter)
        .callPgEndpoint(TestConstants.TEST_CUSTOMER_ID);

    Mockito.doReturn(xyzSampleResponse).when(asyncExecuter)
        .getUserPreference(TestConstants.TEST_CUSTOMER_ID);


    final ActualResponse actualResponse = globalPositionService
        .getGlobalPosition(TestConstants.TEST_CUSTOMER_ID);

    assertNotNull(actualResponse);
}

=====Service
public ActualResponse getGlobalPosition(final String customerId) {

    final CompletableFuture<Map<String, String>> abcSampleResponse = asyncExecuter
        .getProductTypeInfo();
    final CompletableFuture<XyzSample> xyzSampleResponse = asyncExecuter
        .getUserPreference(customerId);

    try {
      return new ResponseDecorator(pgResponse.get(), userPreferenceResponse.get(),
          productTypeInfo.get()).decorate();
    } catch (final Exception e) {
      log.error("Error Occurred while building the response", e);
    }
    return null;
  }

@Component
public class AsyncExecuter {
  public CompletableFuture<XyzSample> callPgEndpoint(final String customerId) {
    return CompletableFuture.completedFuture(xxx);
  }
}
1
Roshan Oswal