Rework solution to handle individual exceptions
This commit is contained in:
parent
43cb6d6289
commit
ec2a32838e
|
@ -1,9 +1,14 @@
|
||||||
package com.baeldung.concurrent.completablefuture;
|
package com.baeldung.concurrent.completablefuture;
|
||||||
|
|
||||||
|
import static java.util.function.Predicate.isEqual;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -16,18 +21,19 @@ import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
public class CombiningCompletableFuturesUnitTest {
|
public class CombiningCompletableFuturesUnitTest {
|
||||||
|
|
||||||
@Test
|
@Mock Logger logger;
|
||||||
public void givenMicroserviceClient_whenCreateResource_thenReturnSuccess() throws ExecutionException, InterruptedException {
|
|
||||||
MicroserviceClient mockMicroserviceA = mock(MicroserviceClient.class);
|
@BeforeEach
|
||||||
when(mockMicroserviceA.createResource(any())).thenReturn(CompletableFuture.completedFuture(123L));
|
void setup() {
|
||||||
CompletableFuture<Long> resultFuture = mockMicroserviceA.createResource("My Resource");
|
logger = mock(Logger.class);
|
||||||
assertEquals(123L, resultFuture.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> clientData() {
|
private static Stream<Arguments> clientData() {
|
||||||
|
@ -41,25 +47,59 @@ public class CombiningCompletableFuturesUnitTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("clientData")
|
@MethodSource("clientData")
|
||||||
public void givenMicroserviceClient_whenMultipleCreateResource_thenCombineResults(List<String> inputs, int expectedSuccess, int expectedFailure) throws ExecutionException, InterruptedException {
|
public void givenMicroserviceClient_whenMultipleCreateResource_thenCombineResults(List<String> inputs, int successCount, int errorCount) {
|
||||||
MicroserviceClient mockMicroservice = mock(MicroserviceClient.class);
|
MicroserviceClient mockMicroservice = mock(MicroserviceClient.class);
|
||||||
when(mockMicroservice.createResource("Good Resource")).thenReturn(CompletableFuture.completedFuture(123L));
|
// Return an identifier of 123 on "Good Resource"
|
||||||
when(mockMicroservice.createResource("Bad Resource")).thenReturn(CompletableFuture.failedFuture(new IllegalArgumentException("Bad Resource")));
|
when(mockMicroservice.createResource("Good Resource"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(123L));
|
||||||
|
// Throw an exception on "Bad Resource"
|
||||||
|
when(mockMicroservice.createResource("Bad Resource"))
|
||||||
|
.thenReturn(CompletableFuture.failedFuture(new IllegalArgumentException("Bad Resource")));
|
||||||
|
|
||||||
|
// Given a list of CompletableFutures from our microservice calls...
|
||||||
List<CompletableFuture<Long>> clientCalls = new ArrayList<>();
|
List<CompletableFuture<Long>> clientCalls = new ArrayList<>();
|
||||||
for (String resource : inputs) {
|
for (String resource : inputs) {
|
||||||
clientCalls.add(mockMicroservice.createResource(resource));
|
clientCalls.add(mockMicroservice.createResource(resource));
|
||||||
}
|
}
|
||||||
CompletableFuture<?>[] clientCallsAsArray = clientCalls.toArray(new CompletableFuture[inputs.size()]);
|
|
||||||
CompletableFuture.allOf(clientCallsAsArray)
|
// When all CompletableFutures are completed (exceptionally or otherwise)...
|
||||||
.exceptionally(ex -> null)
|
Map<Boolean, List<Long>> resultsByValidity = clientCalls.stream()
|
||||||
|
.map(future -> handleFuture(future))
|
||||||
|
.collect(Collectors.partitioningBy(resourceId -> isValidResponse(resourceId)));
|
||||||
|
|
||||||
|
// Then the returned resource identifiers should match what is expected...
|
||||||
|
assertThat(resultsByValidity.getOrDefault(true, List.of()).size()).isEqualTo(successCount);
|
||||||
|
// And the logger mock should be called once for each exception with the expected error message
|
||||||
|
assertThat(resultsByValidity.getOrDefault(false, List.of()).size()).isEqualTo(errorCount);
|
||||||
|
verify(logger, times(errorCount))
|
||||||
|
.error(eq("Encountered error: java.lang.IllegalArgumentException: Bad Resource"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidResponse(long resourceId) {
|
||||||
|
return resourceId != -1L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the given CompletableFuture, handling any exceptions that are thrown.
|
||||||
|
* @param future the CompletableFuture to complete.
|
||||||
|
* @return the resource identifier (-1 if the request failed).
|
||||||
|
*/
|
||||||
|
private Long handleFuture(CompletableFuture<Long> future) {
|
||||||
|
return future
|
||||||
|
.exceptionally(ex -> handleError(ex))
|
||||||
.join();
|
.join();
|
||||||
Map<Boolean, List<CompletableFuture<Long>>> resultsByFailure = clientCalls.stream().collect(Collectors.partitioningBy(CompletableFuture::isCompletedExceptionally));
|
}
|
||||||
assertThat(resultsByFailure.getOrDefault(false, Collections.emptyList()).size()).isEqualTo(expectedSuccess);
|
|
||||||
assertThat(resultsByFailure.getOrDefault(true, Collections.emptyList()).size()).isEqualTo(expectedFailure);
|
private Long handleError(Throwable throwable) {
|
||||||
|
logger.error("Encountered error: " + throwable);
|
||||||
|
return -1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MicroserviceClient {
|
interface MicroserviceClient {
|
||||||
CompletableFuture<Long> createResource(String resourceName);
|
CompletableFuture<Long> createResource(String resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Logger {
|
||||||
|
void error(String message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue