0

Iam writing the Integration test cases and i was stuck at point where i was not able to mock the CompletableFuture.join()

Firstly, I will make an async call and add all the responses to list

@Async("AsyncTaskExecutor")
    public <T> CompletableFuture<ResponseEntity<T>> callCarrierPost(
            ServiceConfig serviceConfig, Class<T> responseType, ExecutionContext executionContext,
            AdapterContext adapterContext) {
        ResponseEntity<T> responseEntity = carrierInvoker.postForObject(
                serviceConfig, responseType, executionContext, adapterContext);
        return CompletableFuture.completedFuture(responseEntity);
    }

Once the async call is made then i will process the responses of the async calls like below,

private <T> List<ResponseEntity<T>> processResponseFutureList(List<CompletableFuture<ResponseEntity<T>>> responseEntityFutureList) {
        List<ResponseEntity<T>> responseEntityList = new ArrayList<>();
        responseEntityFutureList.forEach(responseEntityFuture -> {
            try {
                responseEntityList.add(responseEntityFuture.join());
            } catch (CompletionException ex) {
                if (ex.getCause() instanceof HttpStatusCodeException) {
                    HttpStatusCodeException httpStatusCodeException = ((HttpStatusCodeException) ex.getCause());
                    ResponseEntity<T> response = new ResponseEntity<>((T) httpStatusCodeException.getResponseBodyAsString(),
                            httpStatusCodeException.getResponseHeaders(),
                            httpStatusCodeException.getStatusCode());
                    responseEntityList.add(response);
                } else if (ex.getCause() instanceof ResourceAccessException &&
                        ex.getCause().getCause() instanceof SocketTimeoutException) {
                    responseEntityList.add(getErrorResponseEntity(HttpStatus.SERVICE_UNAVAILABLE,
                            TimeOutException.Code.PROVIDER_TIME_OUT.getVal(), ex.getMessage()));
                } else {
                    responseEntityList.add(getErrorResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR,
                            InternalServerException.Code.INTERNAL_M2_BROKER_ERROR.getVal(), ex.getMessage()));
                }
            }
        });
        return responseEntityList;
    }

From processResponseFutureList method, Iam trying to mock the response of the completableFuture.join() to cover all the exceptional scenarios

So i tried to mock completableFuture, but no luck, it was not throwing an exception with below changes, instead it gives the original response.

@MockBean
    private CompletableFuture completableFuture;

Mockito.when(completableFuture.join())
                .thenReturn(new ResourceAccessException("I/O error on /uri", new SocketTimeoutException("Read Timeout")));

Iam actually new to testing and also never got an chance to work with CompletableFuture

Can someone help to mock the CompletableFuture.join() to throw an exception.

J Harish
  • 193
  • 2
  • 2
  • 5

1 Answers1

0

In general, don't mock types you don't own. In particular, CompletableFuture is an enormous API with very complicated semantics and I can't recommend mocking it. (Your team's opinion may vary, but CompletableFuture's large and non-encapsulated API is known as a design issue, particularly in how a CompletableFuture can be controlled from outside its source.)

Furthermore, join will never return a ResourceAccessException, nor throw one directly. Futures represent the result of some other asynchronous process, probably on another thread; if that process throws a ResourceAccessException, then as in the code you posted, join will throw a CompletionException with a getCause() value that is the underlying ResourceAccessException.

In Java 9 or better, you can use failedFuture as a static factory, passing in the raw ResourceAccessException because the real CompletableFuture implementation will wrap it for you:

// You'll probably need to specify your generics here, but I can't see
// enough of your test to fill them in realistically.
CompletableFuture</* ... */> completableFuture
    = CompletableFuture.failedFuture(new ResourceAccessException(
        "I/O error on /uri",
        new SocketTimeoutException("Read Timeout")));

In Java 8, in absence of a static factory as in the SO question "CompletableFuture already completed with an exception", just create a real CompletableFuture and complete it exceptionally (taking advantage of the aforementioned external-control design issue):

CompletableFuture</* ... */> completableFuture
    = new CompletableFuture</* ... */>();
completableFuture.completeExceptionally(
    new ResourceAccessException(/*...*/));
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251