Search code examples
javajunitjava-8junit4completable-future

How can I test exception in completable future?


I have been converting some code to be asynchronous. The original unit test used the annotation @Test(expected = MyExcpetion.class) but I don't think this will work because the exception I want to assert on is wrapped in java.util.concurrent.ExcutionException . I did try calling my future like this but my assertion is still failing and I don't love that I had to add in return null

myApiCall.get(123).exceptionally((ex) -> {
 assertEquals(ex.getCause(),MyCustomException.class)
 return null
}

I also tried this flavor but still not working

myApiCall.get(123).exceptionally((ex) -> {
 assertThat(ex.getCause())
  .isInstanceOF(MyException.class)
  .hasMessage("expected message etc")
 return null;
}

My API just throws exception if it can't find id. How should I be properly testing this? Can I use that original annotation in anyway?

my api call reaches out to db when run. In this test I am setting up my future to return an error so it doesn't actually try to communicate with anything. the code under test looks like this

 public class myApiCall  { 
   public completableFuture get(final String id){
   return myService.getFromDB(id)
    .thenApply( 
       //code here looks at result and if happy path then returns it after 
       //doing some transformation 
      //otherwise it throws exception 
   )
  }
 }

in the unit test I force myService.getFromDB(id) to return bad data so I can test exception and also keep this a unit test don't reach out to db etc.


Solution

  • Let's assume your API throws if called with 0:

    public static CompletableFuture<Integer> apiCall(int id) {
      return CompletableFuture.supplyAsync(() -> {
        if (id == 0) throw new RuntimeException("Please not 0!!");
        else return id;
      });
    }
    

    You can test that it works as expected with the following code (I'm using TestNG but I suspect it won't be too difficult to translate into a JUnit test):

    @Test public void test_ok() throws Exception {
      CompletableFuture<Integer> result = apiCall(1);
      assertEquals(result.get(), (Integer) 1);
    }
    
    @Test(expectedExceptions = ExecutionException.class,
          expectedExceptionsMessageRegExp = ".*RuntimeException.*Please not 0!!")
    public void test_ex() throws Throwable {
      CompletableFuture<Integer> result = apiCall(0);
      result.get();
    }
    

    Note that the second test uses the fact that the ExecutionException message will contain the original exception type and message and captures the expectation with a regex. If you can't do that with JUnit, you can call result.get() in a try/catch block and call throw e.getCause(); in the catch block. In other words, something like this:

    @Test(expectedExceptions = RuntimeException.class,
          expectedExceptionsMessageRegExp = "Please not 0!!")
    public void test_ex() throws Throwable {
      CompletableFuture<Integer> result = apiCall(0);
      try {
        result.get();
      } catch (ExecutionException e) {
        throw e.getCause();
      }
    }