我一直在将一些代码转换为异步。原始单元测试使用了注释@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等。
答案 0 :(得分:5)
假设使用0
:
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());