Spring Boot测试因缺失PopulatingService Bean启动失败求助
问题概述
测试Spring REST API时,ApplicationContext加载失败,核心报错为:容器中不存在solita.citybike.services.PopulatingService类型的合格Bean。已在主类添加@EntityScan("solita.citybike"),怀疑与控制器注入两个Service有关,但无法定位具体原因。
错误栈
2023-05-10T22:45:38.615+03:00 ERROR 10504 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener] to prepare test instance [solita.citybike.JourneyControllerTest@77c1e611] java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@93824eb testClass = solita.citybike.JourneyControllerTest, locations = [], classes = [solita.citybike.controllers.JourneyController, solita.citybike.services.JourneyService, solita.citybike.repositories.JourneyRepository], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@792b749c, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@9da1, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@595a413a, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@87e31c, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@29d80d2b, [ImportsContextCustomizer@7af52ec7 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration, org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration, org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration, org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration, org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration, org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration, com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) ~[junit-rt.jar:na] at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) ~[junit-rt.jar:na] at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54) ~[junit-rt.jar:na] Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'journeyController': Unsatisfied dependency expressed through field 'populatingService': No qualifying bean of type 'solita.citybike.services.PopulatingService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:712) ~[spring-beans-6.0.6.jar:6.0.6] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:692) ~[spring-beans-6.0.6.jar:6.0.6] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:133) ~[spring-beans-6.0.6.jar:6.0.6] org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) ~[spring-boot-test-3.0.4.jar:3.0.4] at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:59) ~[spring-core-6.0.6.jar:6.0.6] at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:47) ~[spring-core-6.0.6.jar:6.0.6] at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1388) ~[spring-boot-3.0.4.jar:3.0.4] at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:545) ~[spring-boot-test-3.0.4.jar:3.0.4] at Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'solita.citybike.services.PopulatingService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1812) ~[spring-beans-6.0.6.jar:6.0.6] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1371) ~[spring-beans-6.0.6.jar:6.0.6] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1325) ~[spring-beans-6.0.6.jar:6.0.6] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:709) ~[spring-beans-6.0.6.jar:6.0.6] ... 98 common frames omitted java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@93824eb testClass = solita.citybike.JourneyControllerTest, locations = [], classes = [solita.citybike.controllers.JourneyController, solita.citybike.services.JourneyService, solita.citybike.repositories.JourneyRepository], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@792b749c, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@9da1, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@595a413a, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@87e31c, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@29d80d2b, [ImportsContextCustomizer@7af 52ec7 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@36804139, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@24959ca4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@a4e0ef72, org.springframework.boot.test.context.SpringBootTestAnnotation@ce9cd5cb], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:142) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127) at ... 73 more Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'solita.citybike.services.PopulatingService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1812) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1371) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1325) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:709) ... 98 more Process finished with exit code -1
相关代码
主类代码
@SpringBootApplication @EntityScan("solita.citybike") public class JourneyBackend { public static void main(String [] args){ SpringApplication.run(JourneyBackend.class, args); } }
Controller代码
@RestController @RequestMapping("journeys") public class JourneyController { @Autowired public PopulatingService populatingService; @Autowired public JourneyService journeyService; @GetMapping public ResponseEntity<List<BikeJourney>> getAllJourneys( @RequestParam(defaultValue = "0") Integer pageNumber, @RequestParam(defaultValue = "15") Integer pageSize, @RequestParam(defaultValue = "id") String sortBy ){ List<BikeJourney> journeyList = this.journeyService.getAllJourneys(pageNumber, pageSize, sortBy); return new ResponseEntity<List<BikeJourney>>(journeyList, new HttpHeaders(), HttpStatus.OK); } @GetMapping("/hello") public @ResponseBody String greeting() { return this.journeyService.test(); } @PostMapping("/populate") public ResponseEntity<String> add(){ if(this.populatingService.populateData()){ return ResponseEntity.status(HttpStatus.OK).body("Data read from file to db succesfully"); } return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Reading data from file failed"); }
JourneyService代码
@Service public class JourneyService { @Autowired private JourneyRepository journeyRepository; @Autowired private StationRepository stationRepository; public List<BikeJourney> getAllJourneys(Integer pageNumber, Integer pageSize, String sortBy) { Pageable paging = PageRequest.of(pageNumber, pageSize, Sort.by(sortBy)); Page<BikeJourney> pagedResult = journeyRepository.findAll(paging); if(pagedResult.isEmpty()){ return new ArrayList<>(); } return pagedResult.getContent(); } public String test() { return "cat"; } }
JourneyRepository代码
@Repository public interface JourneyRepository extends PagingAndSortingRepository<BikeJourney, Integer> { List<BikeJourney> findAll(); Iterable<BikeJourney> saveAll(Iterable<BikeJourney> validJourneys); Iterable<BikeJourney> deleteAll(); }
解决方案
核心原因
从错误栈的WebMergedContextConfiguration可以看出,测试上下文仅加载了JourneyController、JourneyService、JourneyRepository三类组件,但JourneyController依赖的PopulatingService不在加载列表中;同时需确认PopulatingService本身是否添加了Spring组件注解,否则容器不会将其注册为Bean。
修复步骤
为PopulatingService添加组件注解
确保solita.citybike.services.PopulatingService类上添加@Service注解,示例:@Service public class PopulatingService { // 类实现逻辑 }调整测试类上下文配置
- 若使用
@WebMvcTest:该注解默认仅加载控制器相关Bean,需手动导入PopulatingService@WebMvcTest(JourneyController.class) @Import({JourneyService.class, PopulatingService.class}) public class JourneyControllerTest { // 测试代码 } - 若需完整上下文:改用
@SpringBootTest替代@WebMvcTest,自动扫描所有组件(适合集成测试)@SpringBootTest @AutoConfigureMockMvc public class JourneyControllerTest { // 测试代码 }
- 若使用
验证组件扫描范围
@SpringBootApplication默认扫描主类所在包及其子包。若PopulatingService在solita.citybike.services包下,主类在solita.citybike包下,默认扫描范围已覆盖;若包结构不一致,需在主类添加@ComponentScan("solita.citybike.services")确保扫描到该类。
内容的提问来源于stack exchange,提问作者Eckersley

