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

SpringBoot测试无法加载test目录下application.yml配置属性如何修复

问题根因

测试中配置属性加载失效的核心原因是注解逻辑冲突:

  • 类上的@SpringBootTest注解本来会启动Spring测试上下文,自动加载src/test/resources路径下的application.yml配置,但你并没有从Spring容器中获取EmployeeServiceImpl的实例
  • 你给employeeServiceImpl字段加了@InjectMocks注解,这个是Mockito提供的注解,作用是直接通过反射new一个目标类的普通实例,仅注入标记了@Mock的依赖对象,整个过程完全不经过Spring Bean的生命周期,不会处理类中标记了@Value的字段,自然读不到yml里的配置。
    另外原测试代码中verify(EmployeeService, times(1))的写法有误:verify方法的第一个参数必须是mock生成的实例对象,不能直接传入接口类。
可行修复方案

根据测试需求二选一即可,两种方案都能满足你「不校验返回值、仅确认目标方法被正确调用」的要求。

方案1:纯单元测试(启动速度快,不加载完整Spring上下文)

不需要启动Spring容器,也不需要依赖yml配置文件,直接通过反射给@Value对应的字段注入测试值,同时mock掉所有外部依赖避免真实HTTP请求,适合快速验证Service内部逻辑。

  1. 移除测试类上和Spring上下文、MockMvc相关的无用注解和代码(如果你没有其他接口测试需求的话),添加Mockito扩展注解
  2. 用@InjectMocks创建EmployeeServiceImpl实例,通过ReflectionTestUtils手动给三个配置字段赋值
  3. 用Mockito的构造函数mock能力,拦截Service内部直接new出来的EmpRestClientFactory实例,避免真实调用外部接口
    示例代码:
@ExtendWith(MockitoExtension.class)
class SampleTest {

  @InjectMocks
  private EmployeeServiceImpl employeeServiceImpl;

  @Mock
  private EmpRestClient empRestClient;
  @Mock
  private EmpSuperClient empSuperClient;

  @BeforeEach
  void setup() {
    // 手动注入原@Value对应的配置值,不需要加载yml
    ReflectionTestUtils.setField(employeeServiceImpl, "username", "testEmp");
    ReflectionTestUtils.setField(employeeServiceImpl, "password", "testPwd");
    ReflectionTestUtils.setField(employeeServiceImpl, "url", "http://test.emp.local");
  }

  @Test
  void testCreateIssueCall() {
    String summary = "summary";
    String description = "description";

    // mock内部new出来的EmpRestClientFactory实例
    try (MockedConstruction<EmpRestClientFactory> mockedFactory = mockConstruction(
            EmpRestClientFactory.class,
            (factory, ctx) -> when(factory.createWithBasicHttpAuthentication(any(), anyString(), anyString()))
                    .thenReturn(empRestClient)
    )) {
      when(empRestClient.getProject()).thenReturn(empSuperClient);
      IssueCreationResponse mockResp = mock(IssueCreationResponse.class);
      when(empSuperClient.createIssue(any(IssueInput.class)))
              .thenReturn(CompletableFuture.completedFuture(mockResp));
      when(mockResp.getKey()).thenReturn("TEST-001");

      employeeServiceImpl.createIssue(summary, description);
      // 校验目标方法被正确调用
      verify(empSuperClient, times(1)).createIssue(any(IssueInput.class));
    }
  }
}

方案2:Spring集成测试(真实加载test目录下的application.yml配置)

如果你需要验证配置加载逻辑,可以走完整Spring上下文流程,注意不要手动new Service实例,直接从容器中获取注入的Bean即可:

  1. 移除employeeServiceImpl字段上的@InjectMocks注解,改用@Autowired从Spring容器中获取实例,此时实例的@Value字段会被自动注入yml中的配置值
  2. 用@MockBean替换容器中真实的HTTP客户端相关Bean,避免测试时真实发起外部请求
  3. 确认@SpringBootTest中指定的启动类ABCApi.class的组件扫描范围覆盖EmployeeServiceImpl所在的包,如果没有覆盖可以额外加@ComponentScan指定扫描路径
    示例代码:
@SpringBootTest(classes = ABCApi.class)
@AutoConfigureMockMvc
class SampleTest {

  @Autowired
  private WebApplicationContext context;

  protected MockMvc mockMvc;

  @MockBean
  private EmpRestClientFactory empRestClientFactory;
  @MockBean
  private EmpRestClient empRestClient;
  @MockBean
  private EmpSuperClient empSuperClient;

  // 直接从Spring容器取Bean,自动注入@Value配置
  @Autowired
  private EmployeeServiceImpl employeeServiceImpl;

  @BeforeEach
  public void setup() {
    mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(SecurityMockMvcConfigurers.springSecurity())
            .build();

    // mock外部调用逻辑,避免空指针
    when(empRestClientFactory.createWithBasicHttpAuthentication(any(), anyString(), anyString()))
            .thenReturn(empRestClient);
    when(empRestClient.getProject()).thenReturn(empSuperClient);
    IssueCreationResponse mockResp = mock(IssueCreationResponse.class);
    when(empSuperClient.createIssue(any(IssueInput.class)))
            .thenReturn(CompletableFuture.completedFuture(mockResp));
    when(mockResp.getKey()).thenReturn("TEST-001");
  }

  @Test
  void testEmployeeServiceImpl() {
    String summary = "summary";
    String description = "description";
    employeeServiceImpl.createIssue(summary, description);
    verify(empSuperClient, times(1)).createIssue(any(IssueInput.class));
  }
}

注:后续重构为配置类统一加载客户端Bean的形式后,测试代码会更简洁,不需要再处理构造函数mock这类逻辑。

内容的提问来源于stack exchange,提问作者user2238153

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:57:19