I'm learning about java 8 CompletableFuture
and ended up with this.
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:
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!
If I were you, I would simply mock the services A and B and your executors, then inject them thanks to the annotation @InjectMocks
as they are fields of your class.
If you want mock the method execute
of your Executor
, you should rather proceed as next to simply call the method run
of the provided 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());
}
}
@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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With