如何正确Mock实现Runnable接口的类?RecovererService单元测试验证失败求助
问题分析与解决方案
嘿,我来帮你拆解下这个问题——你遇到的Wanted but not invoked错误其实是个典型的Mockito使用误区,咱们一步步理清并解决它。
核心问题根源
你测试类里mock的recovererThread对象根本没被RecovererService实际使用!看一下RecovererService.startService()的代码:它是直接new RecovererThread(...)创建线程实例的,和你在测试中mock的那个recovererThread完全是两个独立对象。所以哪怕真实的RecovererThread.run()已经执行了,你verify的却是一个从未被调用过的mock实例,自然会触发报错。
正确的Mock/测试方案
要解决这个问题,核心是让RecovererService能够依赖注入RecovererThread(或其创建逻辑),这样测试时才能替换成我们的mock对象。这里提供两种优雅的实现方式:
方案1:重构RecovererService,直接注入Runnable
把RecovererThread的创建逻辑从startService()中抽离,改成构造方法注入:
public class RecovererService { private final ConfigServiceProvider serviceProvider; private final ErrorRecordRepository recordRepository; private final SimpleWriterTest simpleWriterTest; private final AuditRecordProcessor itemProcessor; private final Runnable recovererThread; // 改为依赖注入 private ScheduledExecutorService scheduledExecutorService; private boolean isRunning; // 修改构造方法,注入Runnable实例(实际就是RecovererThread) public RecovererService(ConfigServiceProvider serviceProvider, ErrorRecordRepository recordRepository, SimpleWriterTest simpleWriterTest, AuditRecordProcessor itemProcessor, Runnable recovererThread) { this.serviceProvider = serviceProvider; this.recordRepository = recordRepository; this.simpleWriterTest = simpleWriterTest; this.itemProcessor = itemProcessor; this.recovererThread = recovererThread; } @Override public void startService() { if (serviceProvider.isRecoveryServiceAllowed()) { log.info("========================== Starting Recoverer process =========================="); isRunning = true; scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); // 使用注入的线程实例,不再new scheduledExecutorService.scheduleWithFixedDelay(recovererThread, 0, 5000L, TimeUnit.MILLISECONDS); } } }
对应的测试类就可以直接注入mock的Runnable,此时verify就能准确捕获调用:
@RunWith(MockitoJUnitRunner.class) public class RecovererServiceTest { private final ConfigServiceProvider serviceProvider = mock(ConfigServiceProvider.class); private final ErrorRecordRepository recordRepository = mock(ErrorRecordRepository.class); private final SimpleWriterTest simpleWriterTest = mock(SimpleWriterTest.class); private final AuditRecordProcessor itemProcessor = mock(AuditRecordProcessor.class); private final Runnable recovererThread = mock(Runnable.class); private final RecovererService recovererService = new RecovererService(serviceProvider, recordRepository, simpleWriterTest, itemProcessor, recovererThread); @Test public void testRecovererServiceStart() { doReturn(true).when(serviceProvider).isRecoveryServiceAllowed(); recovererService.startService(); verify(recovererThread, timeout(6000)).run(); } }
方案2:使用工厂模式(最小化重构)
如果不想大幅修改RecovererService的构造方法,可以引入线程工厂来解耦创建逻辑:
先定义一个工厂类:
public class RecovererThreadFactory { public Runnable createRecovererThread(ErrorRecordRepository recordRepository, SimpleWriterTest simpleWriterTest, AuditRecordProcessor itemProcessor) { return new RecovererThread(recordRepository, simpleWriterTest, itemProcessor); } }
然后在RecovererService中注入这个工厂:
public class RecovererService { private final ConfigServiceProvider serviceProvider; private final ErrorRecordRepository recordRepository; private final SimpleWriterTest simpleWriterTest; private final AuditRecordProcessor itemProcessor; private final RecovererThreadFactory threadFactory; // 注入工厂 private ScheduledExecutorService scheduledExecutorService; private boolean isRunning; public RecovererService(ConfigServiceProvider serviceProvider, ErrorRecordRepository recordRepository, SimpleWriterTest simpleWriterTest, AuditRecordProcessor itemProcessor, RecovererThreadFactory threadFactory) { this.serviceProvider = serviceProvider; this.recordRepository = recordRepository; this.simpleWriterTest = simpleWriterTest; this.itemProcessor = itemProcessor; this.threadFactory = threadFactory; } @Override public void startService() { if (serviceProvider.isRecoveryServiceAllowed()) { log.info("========================== Starting Recoverer process =========================="); isRunning = true; scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); Runnable thread = threadFactory.createRecovererThread(recordRepository, simpleWriterTest, itemProcessor); scheduledExecutorService.scheduleWithFixedDelay(thread, 0, 5000L, TimeUnit.MILLISECONDS); } } }
测试时mock工厂,让它返回我们预设的mock Runnable:
@RunWith(MockitoJUnitRunner.class) public class RecovererServiceTest { private final ConfigServiceProvider serviceProvider = mock(ConfigServiceProvider.class); private final ErrorRecordRepository recordRepository = mock(ErrorRecordRepository.class); private final SimpleWriterTest simpleWriterTest = mock(SimpleWriterTest.class); private final AuditRecordProcessor itemProcessor = mock(AuditRecordProcessor.class); private final RecovererThreadFactory threadFactory = mock(RecovererThreadFactory.class); private final Runnable recovererThread = mock(Runnable.class); private final RecovererService recovererService = new RecovererService(serviceProvider, recordRepository, simpleWriterTest, itemProcessor, threadFactory); @Test public void testRecovererServiceStart() { doReturn(true).when(serviceProvider).isRecoveryServiceAllowed(); // 让工厂返回我们的mock线程 doReturn(recovererThread).when(threadFactory).createRecovererThread(any(), any(), any()); recovererService.startService(); verify(recovererThread, timeout(6000)).run(); } }
额外注意事项
- 测试完成后记得关闭
ScheduledExecutorService,避免线程泄漏,可以在@After方法中添加recovererService.scheduledExecutorService.shutdownNow();(或者给RecovererService添加一个stopService()方法来管理线程池)。 RecovererThread自身的业务逻辑(比如getFailedRowFromDb、deleteFailedRowFromDb)应该单独写单元测试验证,不要依赖RecovererService的集成测试。
内容的提问来源于stack exchange,提问作者Evgeniy Zelenskiy
相关产品推荐
相关产品推荐

