How can I test exception in completable future?

删除回忆录丶 提交于 2019-12-05 09:42:20

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();
  }
}

You can try also alternative option:

import org.hamcrest.core.IsInstanceOf;
import org.junit.rules.ExpectedException;

public class Test() {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void myApiCallTest() {
        thrown.expect(ExcutionException.class);
        thrown.expectCause(IsInstanceOf.instanceOf(MyException.class));
        thrown.expectMessage("the message you expected");
        myApiCall.get("");
    }
}

Assuming that:

public class myApiCall  { 
    public completableFuture get(final String id) {
        // ...
        throw new ExcutionException(new MyException("the message you expected"))
    }
}

that is easy thing doing in junit-4. Are you remember the @RunWith annotation? Yes, write your own TestRunner to intercept the exception before the junit expected exception processor is invoked, for example:

public class ConcurrentRunner extends BlockJUnit4ClassRunner {

    public ConcurrentRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }


    @Override
    protected Statement possiblyExpectingExceptions(FrameworkMethod method, 
                                                    Object test,
                                                    Statement next) {
        return super.possiblyExpectingExceptions(
               method, test, throwingActualException(next)
        );
    }

    private Statement throwingActualException(Statement next) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    next.evaluate();
                } catch (ExecutionException | CompletionException source) {
                    throw theActualExceptionOf(source);
                }
            }

            private Throwable theActualExceptionOf(Exception source) {
                return source.getCause() != null ? source.getCause() : source;
            }
        };
    }
}

just annotated with @RunWith(ConcurrentRunner.class) on the test, you needn't change your test code at all. for example:

@RunWith(ConcurrentRunner.class)
public class ConcurrentExpectedExceptionTest {

    @Test(expected = IllegalArgumentException.class)
    public void caughtTheActualException() throws Throwable {
        myApiCall().join();
    }

    private CompletableFuture<Object> myApiCall() {
        return CompletableFuture.supplyAsync(() -> {
            throw new IllegalArgumentException();
        });
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!