如何为带存储数据的类配置Spring依赖注入并支持测试Mock?
官方解决方案推荐
第一步:优化Adapter的依赖注入方式(官方最佳实践)
先把Adapter改成全构造函数注入,这是Spring 4.3+官方推荐的方式,既避免字段注入的弊端,又能让依赖关系完全透明,同时兼顾Spring上下文注入和测试灵活性:
@Service @Scope("prototype") final class Adapter { private final AdapterService adapterService; private final Options options; // Spring 4.3+ 无需@Autowired,会自动解析构造函数参数 Adapter(@NotNull AdapterService adapterService, @NotNull Options options) { this.adapterService = adapterService; this.options = options; } }
这样做的好处:
- 单元测试时可以直接手动实例化,无需依赖Spring上下文:
@Test void unitTestAdapter() { AdapterService mockService = mock(AdapterService.class); Options options = new Options(); Adapter adapter = new Adapter(mockService, options); // 直接测试adapter逻辑,验证mock交互 } - Spring上下文依然可以正常创建原型Bean,调用
getBean(Adapter.class, options)时,Spring会自动注入容器中的AdapterService实例,同时使用你传入的Options。
第二步:集成测试中注入Mock服务(Spring官方支持)
如果必须依赖Spring上下文进行集成测试,有两种官方方案:
方案1:Spring Boot测试用@MockBean
在测试类上用@MockBean注解替换容器中的AdapterService为Mock实例,Spring会自动将这个Mock注入到新创建的Adapter原型Bean中:
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.junit.jupiter.api.Test; import static org.mockito.Mockito.verify; @SpringBootTest class AdapterIntegrationTest { @MockBean private AdapterService mockAdapterService; @Test void testAdapterWithMockService() { Options options = new Options(); // 构造你的业务参数 Adapter adapter = applicationContext.getBean(Adapter.class, options); // 调用adapter的业务方法 adapter.doSomething(); // 验证Mock服务的交互是否符合预期 verify(mockAdapterService).expectedMethodCall(); } }
方案2:纯Spring测试用测试专用配置类
如果不用Spring Boot,可通过@Configuration类在测试上下文里注册Mock服务:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.mockito.Mockito.mock; @SpringJUnitConfig @Import(AdapterIntegrationTest.TestConfig.class) class AdapterIntegrationTest { @Configuration static class TestConfig { // 注册Mock的AdapterService到测试上下文 @Bean AdapterService adapterService() { return mock(AdapterService.class); } } @Test void testAdapterWithMockService() { Options options = new Options(); Adapter adapter = applicationContext.getBean(Adapter.class, options); // 执行测试逻辑并验证 } }
为什么不推荐临时方案?
你提到的临时方案(测试专用构造函数、Setter、反射)都存在弊端:
- 测试专用构造函数/Setter会污染生产代码,增加维护成本
- 反射破坏封装性,代码脆弱且可读性差
而上面的官方方案完全遵循Spring的设计原则,既不修改生产代码的语义,又能优雅实现测试需求。
内容的提问来源于stack exchange,提问作者Christian Bueche
相关产品推荐
相关产品推荐

