如何使用 JUnit 测试异步代码
问题
对于以下的异步代码:
public class DemoService {
public CompletableFuture<String> hello() {
return CompletableFuture.supplyAsync(() -> "hello");
}
我们为其编写一个测试用例,并在
CompletableFuture#whenComplete
中判断返回值是否与预期相符,然而即使返回值与预期不符,该测试也不会抛出异常:
@Test
public void exceptionWontBeCaptured() {
DemoService demoService = new DemoService();
demoService.hello()
.whenComplete((result, e) -> {
Assert.assertEquals("wrongValue", result);
}
解决
CompletableFuture#get()
我们可以借助
CompletableFuture#get()
阻塞主线程等待结果的特点,将异步代码转成同步:
@Test
public void blockMainThreadByGet() throws ExecutionException, InterruptedException {
DemoService demoService = new DemoService();
Assert.assertEquals("hello", demoService.hello().get());
}
CountDownLatch
上述方案依赖了一个具体的异步类方法,如果实际的异步类不提供相应的同步方法,上述方案则不适合。针对这种情况,可以借助
CountDownLatch
,初始化一个计数为1的
CountDownLatch
的实例,在测试方法中调用
CountDownLatch#await()
方法进行等待,当异步方法执行成功后在其回调中调用
CountDownLatch#countDown()
使计数器减1变为0,从而继续执行后续的测试判断:
@Test
public void waitOnCountDown() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
DemoService demoService = new DemoService();
AtomicReference<String> actualValue = new AtomicReference<>("");
demoService.hello()
.whenComplete((result, e) -> {
actualValue.set(result);
countDownLatch.countDown();
countDownLatch.await();
Assert.assertEquals("hello", actualValue.get());
}
Awaitility
Awaitility 让测试异步代码变得简单明了:
@Test
public void poweredByAwaitility() {
DemoService demoService = new DemoService();
AtomicReference<String> actualValue = new AtomicReference<>("");
demoService.hello()
.whenComplete((result, e) -> {
actualValue.set(result);