使用@SpringBootTest测试@Service类时@Value注入为空的问题
我使用Spring Boot 3.0.3版本,为Service类编写测试用例时采用@SpringBootTest和JUnit5。测试可以正常运行,但存在一个问题:Service类中通过@Value从配置文件注入的属性值为null,但在测试类中注入同一个属性却能正常获取值。
代码示例:
@Service class Service { @Value("${some.value}") private String someValue; // 运行测试时该值为null } @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class ServiceTest { @Value("${some.value}") private String someValue; // 此处注入正常 @InjectMocks private Service service; // Service类中包含一些Mock对象 }
补充说明:若使用@Autowired替代@InjectMocks则能正常工作,但我需要注入Mock对象。
问题原因
@InjectMocks是Mockito提供的注解,它通过反射直接创建目标类实例,并将标注@Mock/@Spy的对象注入进去,但这个过程完全不经过Spring的依赖注入容器。因此Service类中由Spring负责处理的@Value、@Autowired等注解都不会被解析,导致属性值为null。
而测试类本身是Spring容器管理的Bean,所以测试类中的@Value能正常触发Spring的属性注入流程,获取到配置值。
解决方案
方案1:使用@Autowired + @MockBean(推荐)
这种方式让Service由Spring容器管理,保证@Value正常注入,同时用@MockBean替换Service中的依赖为Mock对象,完全符合Spring测试规范:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class ServiceTest { @Value("${some.value}") private String someValue; // 正常注入 @Autowired private Service service; // Spring管理的Service实例,@Value会自动注入 @MockBean private SomeDependency someDependency; // 替换Service中的依赖为Mock对象 }
@MockBean会将指定的Mock对象注册到Spring容器中,Spring初始化Service时会自动把这个Mock对象注入进去,既保留了Spring的注入能力,又满足了替换依赖为Mock的需求。
方案2:手动注入@Value属性(兼容@InjectMocks)
如果一定要使用@InjectMocks,可以通过反射手动将测试类中获取到的属性值设置到Service实例中:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class ServiceTest { @Value("${some.value}") private String someValue; @InjectMocks private Service service; @Mock private SomeDependency someDependency; @BeforeEach void setUp() { // 用Spring反射工具类设置Service中的属性 ReflectionTestUtils.setField(service, "someValue", someValue); // 初始化Mockito的Mock对象 MockitoAnnotations.openMocks(this); } }
这种方式需要手动处理属性注入,当Service中有多个@Value属性时会比较繁琐,因此更推荐方案1。
内容的提问来源于stack exchange,提问作者Ahmed Bilal

