使用@SpringBootTest替代@DataJPATest时LocationRepository注入为空的问题
LocationRepository注入失败问题排查与解决
问题描述
使用@SpringBootTest(指定随机端口、ApiDbApplication启动类)搭配@AutoConfigureTestDatabase等注解编写LocationIT集成测试类,希望通过RestTemplate调用接口因此未使用@DataJPATest。但运行测试时,标注@Autowired的LocationRepository为空,报错:
No qualifying bean of type 'com.example.apidb.location.LocationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
LocationRepository已添加@Repository注解,测试代码如下:
@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ApiDbApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestPropertySource( locations = "classpath:application.yml") @ExtendWith(SpringExtension.class) @Transactional public class LocationIT { @MockBean CompanyRepository companyRepository; @MockBean ShipmentRepository shipmentRepository; @MockBean ContactRepository contactRepository; private LocationController locationController; private LocationService locationService; @Autowired LocationRepository locationRepository; @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Before public void setup() { locationService = new LocationService(locationRepository); this.locationController = new LocationController(locationService); } @Test public void testAddLocation() { ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule()); Location location = Location.builder() .id(Long.valueOf(7)) .city("Fayetteville") .lat(32.33) .lon(37.49) .name("Big place") .State("Arkansas").build(); ResponseEntity<String> responseEntity = this.restTemplate .postForEntity("http://localhost:" + port + "/api/location/save", location, String.class); ResponseEntity<List<Location>> results = restTemplate.exchange("http://localhost:" + port + "/api/location/list", HttpMethod.GET, null, new ParameterizedTypeReference<List<Location>>(){}); assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); assertEquals(HttpStatus.OK, results.getStatusCode()); assertEquals(Collections.singletonList(location), results.getBody()); } }
原因分析
- 冗余注解干扰上下文初始化:
@RunWith(SpringRunner.class)和@ExtendWith(SpringExtension.class)重复,SpringRunner本质是SpringExtension的别名,Spring Boot 2.2+版本中@SpringBootTest已默认集成SpringExtension,重复添加可能导致上下文加载异常。 - Repository未遵循Spring Data JPA规范:仅添加
@Repository注解不足以让Spring生成代理Bean,LocationRepository必须继承CrudRepository或JpaRepository等Spring Data JPA提供的规范接口。 - 手动实例化组件脱离容器管理:手动new LocationService和LocationController会导致这些实例与Spring容器管理的Bean脱节,且可能干扰容器对Repository的初始化逻辑。
- 组件扫描范围不匹配:若ApiDbApplication所在包不是LocationRepository所在包的父包,Spring无法自动扫描到Repository接口。
- 数据库配置异常:
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)使用application.yml中的数据库配置,若配置错误(如数据库连接失败),Spring Data JPA无法初始化Repository Bean。
解决方案
- 移除冗余注解:删除
@RunWith(SpringRunner.class)和@ExtendWith(SpringExtension.class),保留@SpringBootTest即可。 - 修正Repository接口定义:确保LocationRepository继承Spring Data JPA的规范接口,示例:
@Repository public interface LocationRepository extends JpaRepository<Location, Long> { // 自定义业务方法 }
- 删除手动实例化逻辑:
@SpringBootTest会启动完整Spring上下文,容器中已存在Controller、Service和Repository的实例,无需手动创建。若无需在测试中直接调用Service/Controller,可直接删除相关字段和setup方法;若需要调用,改为@Autowired注入:
@Autowired private LocationController locationController; @Autowired private LocationService locationService;
- 检查组件扫描范围:确保ApiDbApplication所在包是
com.example.apidb(LocationRepository在com.example.apidb.location),若不在同一层级,在启动类添加@ComponentScan(basePackages = "com.example.apidb")指定扫描包。 - 验证数据库配置:确认application.yml中的数据库URL、用户名、密码正确,数据库服务正常运行,Spring能成功建立连接。
修改后的测试代码示例
@SpringBootTest( webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ApiDbApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestPropertySource(locations = "classpath:application.yml") @Transactional public class LocationIT { @MockBean CompanyRepository companyRepository; @MockBean ShipmentRepository shipmentRepository; @MockBean ContactRepository contactRepository; @Autowired LocationRepository locationRepository; @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); @Test public void testAddLocation() { Location location = Location.builder() .city("Fayetteville") .lat(32.33) .lon(37.49) .name("Big place") .State("Arkansas").build(); // 接口若要求JSON格式,需设置请求头并封装请求体 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<Location> request = new HttpEntity<>(location, headers); ResponseEntity<String> responseEntity = restTemplate .postForEntity("http://localhost:" + port + "/api/location/save", request, String.class); ResponseEntity<List<Location>> results = restTemplate.exchange("http://localhost:" + port + "/api/location/list", HttpMethod.GET, null, new ParameterizedTypeReference<List<Location>>(){}); assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); assertEquals(HttpStatus.OK, results.getStatusCode()); // 避免直接用equals比较对象,单独校验字段更可靠 assertNotNull(results.getBody()); assertEquals(1, results.getBody().size()); Location savedLocation = results.getBody().get(0); assertEquals(location.getCity(), savedLocation.getCity()); assertEquals(location.getLat(), savedLocation.getLat(), 0.01); } }
内容的提问来源于stack exchange,提问作者Kado
相关产品推荐
相关产品推荐

