SpringBoot单元测试中无法用@MockBean模拟子类列表问题
批量模拟Spring中父类所有子类Bean的单元测试方案
业务代码中通过@Autowired List<MotherRepositoryClass> repositories可以正常注入所有子类实例,但单元测试时@MockBean仅支持单个Bean,逐个声明又太繁琐,以下两种方案可以实现批量模拟:
方案一:通过ApplicationContext替换已有子类Bean
利用Spring的ApplicationContext获取所有子类Bean,再用Mockito创建模拟实例替换容器中的原Bean:
@SpringBootTest public class YourServiceTest { @Autowired private ApplicationContext applicationContext; @Autowired private YourService yourService; // 待测试的业务类 @BeforeEach void setUp() { // 获取容器中所有MotherRepositoryClass类型的Bean Map<String, MotherRepositoryClass> repoBeanMap = applicationContext.getBeansOfType(MotherRepositoryClass.class); ConfigurableApplicationContext configurableCtx = (ConfigurableApplicationContext) applicationContext; for (Map.Entry<String, MotherRepositoryClass> entry : repoBeanMap.entrySet()) { String beanName = entry.getKey(); Class<? extends MotherRepositoryClass> repoType = entry.getValue().getClass(); // 创建对应类型的模拟实例 MotherRepositoryClass mockRepo = Mockito.mock(repoType); // 替换容器中的原Bean configurableCtx.getBeanFactory().registerSingleton(beanName, mockRepo); } } @Test void testServiceLogic() { // 针对具体子类模拟设置预期行为 ChildRepo1 mockChildRepo = applicationContext.getBean(ChildRepo1.class); Mockito.when(mockChildRepo.queryData()).thenReturn(mockData); // 调用业务方法并断言 yourService.processData(); Mockito.verify(mockChildRepo).queryData(); } }
方案二:自定义测试配置批量注册模拟Bean
通过@TestConfiguration直接批量创建并注册所有子类的模拟Bean,避免替换原有Bean:
@SpringBootTest @Import(RepositoryMockConfig.class) public class YourServiceTest { @Autowired private YourService yourService; @Autowired private List<MotherRepositoryClass> mockRepositories; @TestConfiguration static class RepositoryMockConfig { @Bean public List<MotherRepositoryClass> mockRepositories() throws ClassNotFoundException { // 扫描指定包下所有MotherRepositoryClass的子类 ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AssignableTypeFilter(MotherRepositoryClass.class)); // 替换为你的Repository所在包路径 Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents("com.yourproject.repository"); List<MotherRepositoryClass> mockList = new ArrayList<>(); for (BeanDefinition def : beanDefinitions) { Class<? extends MotherRepositoryClass> repoClass = (Class<? extends MotherRepositoryClass>) Class.forName(def.getBeanClassName()); mockList.add(Mockito.mock(repoClass)); } return mockList; } } @Test void testBatchMock() { // 筛选出目标子类模拟实例并设置行为 mockRepositories.stream() .filter(repo -> repo instanceof ChildRepo2) .findFirst() .ifPresent(repo -> Mockito.when(repo.saveData(Mockito.any())).thenReturn(true)); boolean result = yourService.saveBatchData(testDataList); assert result; } }
关键提示
- 方案一中要注意Bean名称的对应关系,确保替换的是正确的实例
- 方案二中的包扫描路径必须准确,否则会遗漏子类
- 两种方案都支持接口或抽象类的子类模拟,只需调整扫描的父类类型
内容的提问来源于stack exchange,提问作者Martin Jourjon
相关产品推荐
相关产品推荐

