You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 20:42:50