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

使用@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());
    }
}

原因分析

  1. 冗余注解干扰上下文初始化:@RunWith(SpringRunner.class)和@ExtendWith(SpringExtension.class)重复,SpringRunner本质是SpringExtension的别名,Spring Boot 2.2+版本中@SpringBootTest已默认集成SpringExtension,重复添加可能导致上下文加载异常。
  2. Repository未遵循Spring Data JPA规范:仅添加@Repository注解不足以让Spring生成代理Bean,LocationRepository必须继承CrudRepository或JpaRepository等Spring Data JPA提供的规范接口。
  3. 手动实例化组件脱离容器管理:手动new LocationService和LocationController会导致这些实例与Spring容器管理的Bean脱节,且可能干扰容器对Repository的初始化逻辑。
  4. 组件扫描范围不匹配:若ApiDbApplication所在包不是LocationRepository所在包的父包,Spring无法自动扫描到Repository接口。
  5. 数据库配置异常:@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)使用application.yml中的数据库配置,若配置错误(如数据库连接失败),Spring Data JPA无法初始化Repository Bean。

解决方案

  1. 移除冗余注解:删除@RunWith(SpringRunner.class)和@ExtendWith(SpringExtension.class),保留@SpringBootTest即可。
  2. 修正Repository接口定义:确保LocationRepository继承Spring Data JPA的规范接口,示例:
@Repository
public interface LocationRepository extends JpaRepository<Location, Long> {
    // 自定义业务方法
}
  1. 删除手动实例化逻辑:@SpringBootTest会启动完整Spring上下文,容器中已存在Controller、Service和Repository的实例,无需手动创建。若无需在测试中直接调用Service/Controller,可直接删除相关字段和setup方法;若需要调用,改为@Autowired注入:
@Autowired
private LocationController locationController;
@Autowired
private LocationService locationService;
  1. 检查组件扫描范围:确保ApiDbApplication所在包是com.example.apidb(LocationRepository在com.example.apidb.location),若不在同一层级,在启动类添加@ComponentScan(basePackages = "com.example.apidb")指定扫描包。
  2. 验证数据库配置:确认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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:01:14