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

Spring Boot JUnit测试异常:无法读取application-test.yml配置值

问题根源

空指针异常的直接原因是getExchangeUrl生成的URL包含null前缀,本质是Constants类中的静态字段EXCHANGE_API_BASE_URL和EXCHANGE_API_API_KEY未被注入值。当前测试用Mockito的@InjectMocks和@Mock搭建环境,未启动Spring上下文,Constants类的@Component注解和@Value注入逻辑完全没执行,静态字段保持初始null值。

解决方案

方案1:用Spring Boot测试框架加载上下文

通过@SpringBootTest让Spring管理组件,确保@Value正常读取配置:

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.beans.factory.annotation.Autowired;

@SpringBootTest
class RateServiceTest {

    @MockBean
    private RateRepository rateRepository;

    @MockBean
    private RestTemplate restTemplate;

    @Autowired
    private RateService rateService;

    @Test
    void testCalculateRate() {
        // 移除MockitoAnnotations.openMocks(this),SpringBootTest会自动初始化mock
        // ... 原有测试逻辑保持不变
    }
}

方案2:手动初始化静态字段(快速临时修复)

在测试方法开始前手动设置Constants的静态字段值:

@Test
void testCalculateRate() {
    // 手动注入配置值
    Constants.EXCHANGE_API_BASE_URL = "https://api.apilayer.com/exchangerates_data/";
    Constants.EXCHANGE_API_API_KEY = "default-key";

    MockitoAnnotations.openMocks(this);
    // ... 原有测试逻辑
}

方案3:重构Constants类(推荐,符合Spring最佳实践)

避免使用静态字段,改用构造函数注入的非静态类,提升可测试性:

重构Constants类

@Component
public class Constants {

    private final String exchangeApiBaseUrl;
    private final String exchangeApiApiKey;
    private final String exchangeCacheName;
    private final Integer exchangeApiCallLimit;

    public Constants(@Value("${exchange-api.api-url}") String exchangeApiBaseUrl,
                     @Value("${exchange-api.api-key}") String exchangeApiApiKey,
                     @Value("${exchange-api.cache-name}") String exchangeCacheName,
                     @Value("${exchange-api.api-call-limit}") Integer exchangeApiCallLimit) {
        this.exchangeApiBaseUrl = exchangeApiBaseUrl;
        this.exchangeApiApiKey = exchangeApiApiKey;
        this.exchangeCacheName = exchangeCacheName;
        this.exchangeApiCallLimit = exchangeApiCallLimit;
    }

    // Getter方法
    public String getExchangeApiBaseUrl() {
        return exchangeApiBaseUrl;
    }

    public String getExchangeApiApiKey() {
        return exchangeApiApiKey;
    }

    public String getExchangeCacheName() {
        return exchangeCacheName;
    }

    public Integer getExchangeApiCallLimit() {
        return exchangeApiCallLimit;
    }
}

修改RateService依赖注入Constants

@Service
public class RateService {

    private final Constants constants;
    private final RestTemplate restTemplate;
    private final RateRepository rateRepository;

    // 构造函数注入
    public RateService(Constants constants, RestTemplate restTemplate, RateRepository rateRepository) {
        this.constants = constants;
        this.restTemplate = restTemplate;
        this.rateRepository = rateRepository;
    }

    // 生成URL时使用Getter方法
    private String getExchangeUrl(LocalDate rateDate, EnumCurrency base, List<EnumCurrency> targets) {
        String symbols = String.join("%2C", targets.stream().map(EnumCurrency::name).toArray(String[]::new));
        return constants.getExchangeApiBaseUrl() + rateDate + "?symbols=" + symbols + "&base=" + base;
    }
}

调整测试类

class RateServiceTest extends BaseServiceTest {

    @Mock
    private RateRepository rateRepository;

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private Constants constants;

    @InjectMocks
    private RateService rateService;

    @Test
    void testCalculateRate() {
        MockitoAnnotations.openMocks(this);
        
        // Mock Constants的返回值
        when(constants.getExchangeApiBaseUrl()).thenReturn("https://api.apilayer.com/exchangerates_data/");
        when(constants.getExchangeApiApiKey()).thenReturn("default-key");

        // ... 原有测试逻辑保持不变
    }
}

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:22:03