Java CompletableFuture 可用性和单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41858894/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:44:04  来源:igfitidea点击:

CompletableFuture usability and unit test

javaunit-testingtestingjunitmockito

提问by Leo

I'm learning about java 8 CompletableFutureand ended up with this.

我正在学习 Java 8CompletableFuture并最终得到了这个。

Fist of all, what do you think about this lines of code? I need to send request to different services in parallel and then wait for all of them to response and continue working.

首先,您如何看待这行代码?我需要并行向不同的服务发送请求,然后等待所有服务响应并继续工作。

//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();

And even the code is doing what I want, I'm having problems testing the class where that code is. I tried using Mockito and do something like:

甚至代码也在做我想做的,我在测试代码所在的类时遇到了问题。我尝试使用 Mockito 并执行以下操作:

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

Where executor services and services responses are mocking but the test never ends and the thread keeps waiting for something in this line

执行程序服务和服务响应正在模拟但测试永远不会结束并且线程一直在等待此行中的某些内容

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

Any hint on what I'm missing here? Thank you!

关于我在这里缺少什么的任何提示?谢谢!

采纳答案by Nicolas Filotto

If I were you, I would simply mock the services A and B and your executors, then inject them thanks to the annotation @InjectMocksas they are fields of your class.

如果我是你,我会简单地模拟服务 A 和 B 以及你的执行者,然后通过注释注入它们,@InjectMocks因为它们是你的类的字段。

If you want mock the method executeof your Executor, you should rather proceed as next to simply call the method runof the provided Runnable:

如果你想嘲笑的方法execute你的Executor,你应该继续,而在明年简单地调用该方法run提供的Runnable

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

So basically your test would be something like this:

所以基本上你的测试是这样的:

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

回答by Roshan Oswal

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