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内部逻辑。
- 移除测试类上和Spring上下文、MockMvc相关的无用注解和代码(如果你没有其他接口测试需求的话),添加Mockito扩展注解
- 用
@InjectMocks创建EmployeeServiceImpl实例,通过ReflectionTestUtils手动给三个配置字段赋值 - 用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即可:
- 移除
employeeServiceImpl字段上的@InjectMocks注解,改用@Autowired从Spring容器中获取实例,此时实例的@Value字段会被自动注入yml中的配置值 - 用
@MockBean替换容器中真实的HTTP客户端相关Bean,避免测试时真实发起外部请求 - 确认
@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
相关产品推荐
相关产品推荐

