
在Java并发编程中,Future接口的get()方法会声明抛出InterruptedException和ExecutionException。在编写单元测试时,我们经常需要覆盖这些异常捕获块,以确保程序在异步操作失败时能正确响应。然而,直接模拟Future对象并使其get()方法抛出异常,并验证捕获块是否执行,常常会遇到挑战。
常见的错误尝试是使用when(futureMock.get()).thenThrow(CompletableFuture.completedFuture(interruptedException))。这种写法是错误的,因为thenThrow()期望的是一个Throwable对象(即异常实例),而不是一个Future对象。正确的做法是直接传递异常实例,例如thenThrow(interruptedException)。
更深层次的问题在于,即使成功模拟了异常抛出,如何验证一个仅包含异常处理逻辑(如一个空的catch块)的捕获块是否被执行?单元测试需要可观察的副作用来验证行为。
要有效测试Future的异常捕获块,关键在于两点:
立即学习“Java免费学习笔记(深入)”;
为了使异常捕获块可测试,我们需要对其进行改造。假设我们的业务逻辑在捕获到异常时会调用一个服务方法来记录错误。
MyService.java
package de.playground.so74236327;
public class MyService {
public void logError(Exception e) {
// 实际的错误记录逻辑,例如写入日志文件或发送告警
System.err.println("Error logged: " + e.getMessage());
}
}ExampleProductionCode.java (核心异常处理逻辑部分)
package de.playground.so74236327;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ExampleProductionCode {
List<Future<Class>> futureData;
EnginesData enginesData;
MyService myService; // 注入MyService
public ExampleProductionCode(MyService myService) {
this.myService = myService;
futureData = new ArrayList<>();
enginesData = new EnginesData();
// 示例:添加两个真实Future对象,它们内部可能抛出异常
futureData.add(createAndStartAsyncTask());
futureData.add(createAndStartFutureTask());
}
// ... 其他方法,如getFutureData(),getEngineData(),createAndStartAsyncTask(),createAndStartFutureTask() ...
// createAndStartAsyncTask 和 createAndStartFutureTask 内部可以模拟抛出InterruptedException
public void collectAsyncResults() {
futureData.forEach(result -> {
try {
enginesData.add(result.get()); // 尝试获取Future结果
} catch (InterruptedException | ExecutionException e) {
// 关键:在捕获块中调用可验证的服务方法
myService.logError(e);
}
});
}
public class EnginesData {
public void add(Class aclass) {
// 实际的数据处理逻辑
}
}
}在collectAsyncResults()方法中,当result.get()抛出InterruptedException或ExecutionException时,myService.logError(e)方法会被调用。这就是我们测试的切入点。
现在,我们可以编写测试用例来验证异常处理逻辑。
FutureExceptionTest.java
package de.playground.so74236327;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) // 启用Mockito JUnit 5扩展
public class FutureExceptionTest {
@Mock // 使用@Mock注解创建Future的模拟对象
public Future<Class> futureMock;
@Test
public void testInterruptedException() throws ExecutionException, InterruptedException {
// 1. Spy MyService实例
// 我们需要监视MyService的真实行为,以便验证logError是否被调用
MyService mySpy = spy(new MyService());
// 2. 实例化生产代码,并注入spy后的MyService
ExampleProductionCode exampleProductionCode = new ExampleProductionCode(mySpy);
// 获取Future列表,其中包含两个真实Future对象(它们可能内部抛异常)
List<Future<Class>> futureData = exampleProductionCode.getFutureData();
// 3. 准备模拟的异常
InterruptedException interruptedException = new InterruptedException("模拟的Interrupted Exception");
// 4. 配置futureMock的get()方法在调用时抛出异常
// 注意:这里直接thenThrow(interruptedException),而不是thenThrow(CompletableFuture.completedFuture(interruptedException))
when(futureMock.get()).thenThrow(interruptedException);
// 5. 将模拟的futureMock添加到futureData列表中
// 这样在collectAsyncResults执行时,会有一个Future对象是模拟的,会抛出我们设定的异常
futureData.add(futureMock);
// 在collectAsyncResults()调用之前,logError应该没有被调用过
verify(mySpy, times(0)).logError(interruptedException);
// 6. 执行生产代码中包含Future.get()调用的方法
exampleProductionCode.collectAsyncResults();
// 7. 验证logError方法是否被调用
// 假设collectAsyncResults()处理了3个Future:
// - 两个来自ExampleProductionCode内部创建的真实Future,它们可能抛出异常。
// - 一个是我们添加的futureMock,它会抛出interruptedException。
// 因此,logError应该被调用3次(如果所有Future都导致异常)。
verify(mySpy, times(3)).logError(any(Exception.class)); // 验证任何Exception类型的调用
// 如果想验证特定异常,可以这样:
// verify(mySpy, times(1)).logError(interruptedException); // 验证模拟的InterruptedException被捕获
}
}通过以上方法,我们不仅能够成功模拟Future.get()方法抛出InterruptedException或ExecutionException,还能通过在捕获块中引入可验证的副作用,并结合Mockito的spy和verify功能,精确地测试和验证异常处理逻辑是否被正确触发和执行。这种测试策略确保了异步操作在面临异常时,程序能够按照预期进行错误处理,从而提升了代码的健壮性和可靠性。
以上就是使用Mockito测试Java Future对象中的异常处理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号