How can I check for an exception in the final future?

I am converting some code to asynchronous. The initial unit test used the @Test(expected = MyExcpetion.class) annotation @Test(expected = MyExcpetion.class) , but I don't think this will work because the exception I want to state is wrapped in java.util.concurrent.ExcutionException . I really tried to call my future like this, but my statement still fails, and I don't like what I had to add to return null

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

I also tried this fragrance but still didn't work

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

My API just throws an exception if it cannot find the identifier. How should I test this correctly? Can I use this original annotation?

my api call reaches db value on startup. In this test, I set my future to return an error, so in fact it does not try to communicate with anything. the checked code is as follows

  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 unit test, I force myService.getFromDB(id) to return bad data so that I can check the exception, and also save this unit test, not reaching db, etc.

+5
source share
3 answers

Suppose your API is called when you call 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 verify that it works as expected with the following code (I use TestNG, but I suspect it won't be too hard 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 wait with a regular expression. If you cannot do this using JUnit, you can call result.get() in the 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(); } } 
+4
source

which is easy to do in junit-4. Do you remember the @RunWith annotation? Yes, write your own TestRunner to catch the exception before calling the expected junit exception processor, 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) tag @RunWith(ConcurrentRunner.class) , you don’t need to change your test code at all. eg:

 @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(); }); } } 
+1
source

You can also try an alternative:

 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")) } } 
0
source

Source: https://habr.com/ru/post/1268992/


All Articles