diff --git a/core-java-modules/core-java-concurrency-2/src/test/java/com/baeldung/concurrent/completablefuture/CombiningCompletableFuturesUnitTest.java b/core-java-modules/core-java-concurrency-2/src/test/java/com/baeldung/concurrent/completablefuture/CombiningCompletableFuturesUnitTest.java new file mode 100644 index 0000000000..2c157b3ab2 --- /dev/null +++ b/core-java-modules/core-java-concurrency-2/src/test/java/com/baeldung/concurrent/completablefuture/CombiningCompletableFuturesUnitTest.java @@ -0,0 +1,90 @@ +package com.baeldung.concurrent.completablefuture; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CombiningCompletableFuturesUnitTest { + + private final Logger logger = mock(Logger.class); + + private static Stream clientData() { + return Stream.of( + Arguments.of(List.of("Good Resource"), 1, 0), + Arguments.of(List.of("Bad Resource"), 0, 1), + Arguments.of(List.of("Good Resource", "Bad Resource"), 1, 1), + Arguments.of(List.of("Good Resource", "Bad Resource", "Good Resource", "Bad Resource", "Good Resource"), 3, 2) + ); + } + + @ParameterizedTest + @MethodSource("clientData") + public void givenMicroserviceClient_whenMultipleCreateResource_thenCombineResults(List inputs, int successCount, int errorCount) { + MicroserviceClient mockMicroservice = mock(MicroserviceClient.class); + // Return an identifier of 123 on "Good 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> clientCalls = new ArrayList<>(); + for (String resource : inputs) { + clientCalls.add(mockMicroservice.createResource(resource)); + } + + // When all CompletableFutures are completed (exceptionally or otherwise)... + Map> resultsByValidity = clientCalls.stream() + .map(this::handleFuture) + .collect(Collectors.partitioningBy(resourceId -> resourceId != -1L)); + + // Then the returned resource identifiers should match what is expected... + List validResults = resultsByValidity.getOrDefault(true, List.of()); + assertThat(validResults.size()).isEqualTo(successCount); + + // And the logger mock should be called once for each exception with the expected error message + List invalidResults = resultsByValidity.getOrDefault(false, List.of()); + assertThat(invalidResults.size()).isEqualTo(errorCount); + verify(logger, times(errorCount)) + .error(eq("Encountered error: java.lang.IllegalArgumentException: Bad Resource")); + } + + /** + * 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 future) { + return future + .exceptionally(this::handleError) + .join(); + } + + private Long handleError(Throwable throwable) { + logger.error("Encountered error: " + throwable); + return -1L; + } + + interface MicroserviceClient { + CompletableFuture createResource(String resourceName); + } + + interface Logger { + void error(String message); + } +}