如何用JUnit、Mockito或PowerMockito测试Java ExecutorService关闭逻辑?
覆盖ExecutorService关闭逻辑全分支的JUnit测试方案
你已经实现了基于ContextClosedEvent的ExecutorService关闭逻辑,但直接调用onApplicationEvent方法后,代码覆盖率仅覆盖首行。要覆盖awaitTermination和shutdownNow相关的所有分支,核心思路是用Mockito模拟ExecutorService的行为,控制各个方法的返回值或异常,从而触发不同分支逻辑。
测试依赖准备
确保测试类引入JUnit 5和Mockito依赖(如mockito-core、mockito-junit-jupiter),直接在测试类中使用Mockito注解即可。
具体测试用例
1. 场景:线程池正常终止(awaitTermination返回true)
此场景走最外层正常逻辑,不会触发shutdownNow。
测试代码:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.event.ContextClosedEvent; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class YourEventListenerTest { @Mock private ExecutorService execService; @InjectMocks private YourEventListener eventListener; // 替换为你的事件监听类 @Test void onApplicationEvent_正常终止() throws InterruptedException { // 模拟awaitTermination在60秒内返回true when(execService.awaitTermination(60, TimeUnit.SECONDS)).thenReturn(true); // 触发事件处理方法 eventListener.onApplicationEvent(mock(ContextClosedEvent.class)); // 验证方法调用次数 verify(execService, times(1)).shutdown(); verify(execService, times(1)).awaitTermination(60, TimeUnit.SECONDS); verify(execService, never()).shutdownNow(); // shutdownNow不会被调用 // 验证日志输出 Logger log = LoggerFactory.getLogger(YourEventListener.class); verify(log, times(1)).info(contains("ExecutorService shutdown hook called!")); } }
2. 场景:首次等待超时,二次等待成功终止
此场景会触发shutdownNow,但第二次awaitTermination返回true,不会输出错误日志。
测试代码:
@Test void onApplicationEvent_首次等待超时_二次成功终止() throws InterruptedException { // 首次awaitTermination返回false,第二次返回true when(execService.awaitTermination(60, TimeUnit.SECONDS)) .thenReturn(false) .thenReturn(true); eventListener.onApplicationEvent(mock(ContextClosedEvent.class)); verify(execService, times(1)).shutdown(); verify(execService, times(2)).awaitTermination(60, TimeUnit.SECONDS); verify(execService, times(1)).shutdownNow(); Logger log = LoggerFactory.getLogger(YourEventListener.class); verify(log, times(1)).info(contains("ExecutorService shutdown hook called!")); verify(log, never()).error(contains("Pool did not terminate")); }
3. 场景:两次等待都超时,输出错误日志
此场景会触发shutdownNow,且第二次awaitTermination仍返回false,会输出错误日志。
测试代码:
@Test void onApplicationEvent_两次等待都超时() throws InterruptedException { // 两次awaitTermination都返回false when(execService.awaitTermination(60, TimeUnit.SECONDS)).thenReturn(false); eventListener.onApplicationEvent(mock(ContextClosedEvent.class)); verify(execService, times(1)).shutdown(); verify(execService, times(2)).awaitTermination(60, TimeUnit.SECONDS); verify(execService, times(1)).shutdownNow(); Logger log = LoggerFactory.getLogger(YourEventListener.class); verify(log, times(1)).info(contains("ExecutorService shutdown hook called!")); verify(log, times(1)).error(contains("Pool did not terminate")); }
4. 场景:awaitTermination抛出InterruptedException
此场景会进入catch块,触发shutdownNow并中断当前线程。
测试代码:
@Test void onApplicationEvent_等待被中断() throws InterruptedException { // 模拟awaitTermination抛出InterruptedException when(execService.awaitTermination(60, TimeUnit.SECONDS)).thenThrow(InterruptedException.class); eventListener.onApplicationEvent(mock(ContextClosedEvent.class)); verify(execService, times(1)).shutdown(); verify(execService, times(1)).awaitTermination(60, TimeUnit.SECONDS); verify(execService, times(1)).shutdownNow(); // 验证当前线程被中断 assert Thread.currentThread().isInterrupted(); }
注意事项
- 将
YourEventListener替换为你实际的事件监听类名称; - 若要精准断言日志,可使用
slf4j-test工具捕获日志输出; - 所有测试用例无需手动处理
InterruptedException,Mockito会模拟异常抛出逻辑。
内容的提问来源于stack exchange,提问作者vh1ne
相关产品推荐
相关产品推荐

