JUnit Test for CompletableFuture supplyAsync

85 Views Asked by At

How do I mock and write unit tests for the below function? I am finding it difficult to write unit tests for CompletableFuture. If the function does not cancel the task within 3 minutes, I want to throw an exception in the unit test

private void cancel() throws TimeoutException, ExecutionException, InterruptedException {
    try {
      CompletableFuture.supplyAsync(() -> {
        try {
          cancelTask();// function that cancels a task
        } catch (ApiException ex) {
          Logger.error("Api exception while cancelling a tasks")
        }
        return null
      }).get(3, TimeUnit.MINUTES);
    } catch (final InterruptedException | ExecutionException | TimeoutException e) {
      throw e;
    }
  }
1

There are 1 best solutions below

0
Stefano Riffaldi On

To force CompletableFuture.supplyAsync to return a Timeout Exception in the unit test case after cancelTask() is called, you have to override your cancelTask when you test this case. You can do this in several way:

  1. Add a field to set timeout value via constructor to set is very low (waiting 3 minutes during UnitTest is not good), set cancelTask function protected and override it in the test case.

BusinessClass

    @Slf4j
    @AllArgsConstructor
    public class TaskManager {
        private final int cancelTimeoutInMillis;
    
        public void cancel() throws TimeoutException, ExecutionException, InterruptedException {
            CompletableFuture.supplyAsync(() -> {
                try {
                    cancelTask();// function that cancels a task
                } catch (ApiException ex) {
                    log.error("Api exception while cancelling a tasks");
                }
                return null;
            }).get(cancelTimeoutInMillis, TimeUnit.MILLISECONDS);
        }
    
        @StandardException
        public static class ApiException extends RuntimeException {
        }
    
        protected void cancelTask() {
            //cancel the task
        }
    }

Test class:

    @Slf4j
    class TaskManagerTest {
        @Test
        public void testCancel() throws Exception {
    
            // Create the CompletableFuture under test
            TaskManager manager = new TaskManager(500) {
                @Override
                protected void cancelTask() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            };
    
            // Verify that the expected exception is thrown
            Assertions.assertThatThrownBy(manager::cancel).isInstanceOf(TimeoutException.class);
        }
    
    }
  1. Separate cancelTask from async executor via dependency injection and mock it ( or override cancelTask )

BusinessClasses

@AllArgsConstructor
@Getter
public class TaskExecutor {
    private final int cancelTimeoutInMillis;

    public void cancelTask() throws TaskManager.ApiException {
    }
}

@Slf4j
@AllArgsConstructor
public class TaskManager {
    private final TaskExecutor taskExecutor;

    public void cancel() throws TimeoutException, ExecutionException, InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            try {
                taskExecutor.cancelTask();// function that cancels a task
            } catch (ApiException ex) {
                log.error("Api exception while cancelling a tasks");
            }
            return null;
        }).get(taskExecutor.getCancelTimeoutInMillis(), TimeUnit.MILLISECONDS);
    }

    @StandardException
    public static class ApiException extends RuntimeException {
    }
}

Test Class

@Slf4j
class TaskManagerTest {
    @Test
    public void testCancel() throws Exception {

        // Create the CompletableFuture under test
        TaskExecutor taskExecutor = new TaskExecutor(500){
            @Override
            public void cancelTask() throws TaskManager.ApiException {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        TaskManager manager = new TaskManager(taskExecutor);

        // Verify that the expected exception is thrown
        Assertions.assertThatThrownBy(manager::cancel).isInstanceOf(TimeoutException.class);
    }
}