如何在可完成的未来测试异常?

时间:2017-06-19 14:58:22

标签: java junit java-8 junit4 completable-future

我一直在将一些代码转换为异步。原始单元测试使用了注释@Test(expected = MyExcpetion.class),但我认为这不会起作用,因为我要断言的异常包含在java.util.concurrent.ExcutionException中。我确实尝试过像这样调用我的未来,但我的断言仍然失败,我不喜欢我必须添加return null

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

我也试过这种味道但仍然无法正常工作

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

如果找不到ID,我的API会抛出异常。我应该如何正确测试?无论如何我可以使用原始注释吗?

我的api调用在运行时到达db。在这个测试中,我正在设置我的未来以返回错误,因此它实际上并没有尝试与任何东西进行通信。被测代码看起来像这样

 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 
   )
  }
 }
单元测试中的

我强制myService.getFromDB(id)返回错误的数据,这样我就可以测试异常并保持单元测试不会到达db等。

4 个答案:

答案 0 :(得分:5)

假设使用0

进行调用,则会抛出您的API
public static CompletableFuture<Integer> apiCall(int id) {
  return CompletableFuture.supplyAsync(() -> {
    if (id == 0) throw new RuntimeException("Please not 0!!");
    else return id;
  });
}

您可以使用以下代码测试它是否按预期工作(我正在使用TestNG,但我怀疑转换为JUnit测试并不太难):

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

请注意,第二个测试使用的事实是ExecutionException消息将包含原始异常类型和消息,并使用正则表达式捕获期望值。如果你不能用JUnit做到这一点,你可以在try / catch块中调用result.get()并在catch块中调用throw e.getCause();。换句话说,就像这样:

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

答案 1 :(得分:2)

您还可以尝试其他选项:

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

假设:

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

答案 2 :(得分:1)

在junit-4中这很容易。你还记得@RunWith注释吗?是的,编写自己的TestRunner以在调用junit预期异常处理器之前拦截异常,例如:

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

在测试中仅使用@RunWith(ConcurrentRunner.class)注释,您根本不需要更改测试代码。例如:

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

答案 3 :(得分:1)

假设您有一个类,并且想测试一个返回可完成的将来的方法:

  public class A {
    private final Api api;

    public A(Api api) { this.api = api;}

    public CompletableFuture<Void> execute(Integer input) {
       final CompletableFuture<Void> future = api.execute(input)
                .thenApplyAsync(result -> doSomething())
                .exceptionally(ex -> doFailure());
       return future;
    }
 }

要测试“ doSomething()”的执行情况,则可以使用模仿并执行以下操作:

     // prepare test
     final Api api = mock(Api.class)
     final A a = new A(api);
     when(api.execute(any(Integer.class)))
          .thenReturn(CompletableFuture.completedFuture(null));

     // execute
     final CompletableFuture<Void> result = a.execute(input);

     // validate
     ...
         

要测试“ doFailure”,请执行以下操作:

when(api.execute(any(Integer.class))).thenAnswer(answer -> {
     CompletableFuture<Void> future = new CompletableFuture<>();
     future.completeExceptionally(new RuntimeException());
     return future;
});

// execute
final CompletableFuture<Void> result = a.execute(input);

// validate
assertTrue(result.isCompletedExceptionally());