如何在可完成的未来测试异常?
How can I test exception in 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;
}
我的 API 只是在找不到 id 时抛出异常。我应该如何正确测试这个?无论如何我都可以使用那个原始注释吗?
我的 api 呼叫在 运行 时到达数据库。在这个测试中,我将我的 future 设置为 return 一个错误,因此它实际上不会尝试与任何东西进行通信。被测代码如下所示
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)
到 return 错误数据,这样我就可以测试异常并保持这个单元测试,不要联系数据库等
让我们假设如果使用 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();
}
}
您也可以尝试其他选项:
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"))
}
}
这在 junit-4 中很容易做到。您还记得 @RunWith
注释吗?是的,自己写TestRunner
在调用junit expected exception processor之前拦截异常,例如:
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();
});
}
}
假设您有一个 class 并且您想要测试一个 returns 可完成未来的方法:
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()”的执行,您可以使用 mockito 并执行以下操作:
// 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());
我一直在将一些代码转换为异步代码。最初的单元测试使用了注释 @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;
}
我的 API 只是在找不到 id 时抛出异常。我应该如何正确测试这个?无论如何我都可以使用那个原始注释吗?
我的 api 呼叫在 运行 时到达数据库。在这个测试中,我将我的 future 设置为 return 一个错误,因此它实际上不会尝试与任何东西进行通信。被测代码如下所示
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)
到 return 错误数据,这样我就可以测试异常并保持这个单元测试,不要联系数据库等
让我们假设如果使用 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();
}
}
您也可以尝试其他选项:
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"))
}
}
这在 junit-4 中很容易做到。您还记得 @RunWith
注释吗?是的,自己写TestRunner
在调用junit expected exception processor之前拦截异常,例如:
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();
});
}
}
假设您有一个 class 并且您想要测试一个 returns 可完成未来的方法:
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()”的执行,您可以使用 mockito 并执行以下操作:
// 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());