1. 引言
在使用Spock测试Spring应用时,我们经常需要替换Spring管理的组件行为。本文将介绍如何用自定义的Stub、Mock或Spy替换Spring自动注入的依赖。虽然示例主要使用Spock的Stub,但这些技术同样适用于Mock和Spy。
2. 环境准备
先添加依赖并创建一个可替换依赖的测试类。
2.1. 依赖配置
首先添加Spring Boot 3的Maven编译依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
接着添加测试依赖(注意版本兼容性):
spring-boot-starter-test
spock-spring
(需用v2.4-M1+以支持Spring 6)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>2.4-M4-groovy-4.0</version>
<scope>test</scope>
</dependency>
2.2. 测试对象
创建AccountService
类,它依赖一个Spring管理的DataProvider
:
@Service
public class AccountService {
private final DataProvider provider;
public AccountService(DataProvider provider) {
this.provider = provider;
}
public String getData(String param) {
return "Fetched: " + provider.fetchData(param);
}
}
创建后续将被替换的DataProvider
:
@Component
public class DataProvider {
public String fetchData(final String input) {
return "data for " + input;
}
}
3. 基础测试类
创建基础测试类验证AccountService
的默认行为:
@ContextConfiguration(classes = [AccountService, DataProvider])
class AccountServiceTest extends Specification {
@Autowired
DataProvider dataProvider
@Autowired
@Subject
AccountService accountService
def "使用真实Bean时获取预期响应"() {
when: "调用数据获取"
def result = accountService.getData("Something")
then: "真实DataProvider返回正确结果"
result == "Fetched: data for Something"
}
}
4. 使用Spock的Spring注解
探索替换依赖的几种方案:
4.1. @StubBeans注解
当需要简单替换依赖且不关心具体返回值时,用@StubBeans
自动创建Stub:
@StubBeans(DataProvider)
@ContextConfiguration(classes = [AccountService, DataProvider])
class AccountServiceStubBeansTest extends Specification {
@Autowired
@Subject
AccountService accountService
// ...
}
✅ 特点:
- 无需手动创建Stub
- 自动返回空字符串(
""
)
测试用例验证空字符串返回:
def "使用@StubBeans时自动注入空响应Stub"() {
when: "调用数据获取"
def result = accountService.getData("Something")
then: "Stub返回空字符串"
result == "Fetched: "
}
多依赖替换语法:
@StubBeans([DataProvider, MySecondDependency, MyThirdDependency])
4.2. @SpringBean注解
需要自定义响应时,手动创建Stub并用@SpringBean
注入:
@SpringBean
DataProvider mockProvider = Stub()
⚠️ 注意:必须声明具体类型(如DataProvider
),不能用def
或Object
完整测试示例:
@ContextConfiguration(classes = [AccountService, DataProvider])
class AccountServiceSpringBeanTest extends Specification {
// ...
def "使用@SpringBean注入自定义Stub"() {
given: "设置Stub响应"
mockProvider.fetchData(_ as String) >> "42"
when: "调用数据获取"
def result = accountService.getData("Something")
then: "Stub覆盖原始依赖"
result == "Fetched: 42"
}
}
4.3. @SpringSpy注解
需要部分保留真实对象行为时,用@SpringSpy
包装成Spy:
@SpringSpy
DataProvider mockProvider
基础测试(验证方法调用但保留原始行为):
@ContextConfiguration(classes = [AccountService, DataProvider])
class AccountServiceSpringSpyTest extends Specification {
@SpringSpy
DataProvider dataProvider
@Autowired
@Subject
AccountService accountService
def "使用@SpringSpy保留原始行为"() {
when: "调用数据获取"
def result = accountService.getData("Something")
then: "验证调用且返回原始结果"
1 * dataProvider.fetchData(_)
result == "Fetched: data for Something"
}
}
覆盖行为测试:
def "使用@SpringSpy覆盖方法响应"() {
when: "调用数据获取"
def result = accountService.getData("Something")
then: "验证调用并返回自定义结果"
1 * dataProvider.fetchData(_) >> "spied"
result == "Fetched: spied"
}
4.4. 在@SpringBootTest中使用@SpringBean
将@SpringBean
用于@SpringBootTest
测试:
@SpringBootTest
class AccountServiceSpringBootTest extends Specification {
// 测试方法与AccountServiceSpringBeanTest相同
}
⚠️ 踩坑点:需创建主测试类避免初始化失败
@SpringBootApplication
class TestApplication {
static void main(String[] args) {
SpringApplication.run(TestApplication, args)
}
}
排除自动配置(如DataSource):
@SpringBootTest(properties = ["spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"])
4.5. 上下文缓存问题
@SpringBean
会禁用Spring上下文缓存,导致测试变慢:
❌ 影响:
- 每个测试实例创建独立Mock
- 阻止上下文复用
✅ 建议:
- 谨慎使用
@SpringBean
- 优先用
@StubBeans
处理简单场景
5. 总结
本文介绍了三种替换Spring依赖的核心方案:
场景 | 推荐方案 | 特点 |
---|---|---|
简单替换(空响应) | @StubBeans |
零配置,自动生成Stub |
自定义响应 | @SpringBean |
完全控制Mock行为 |
部分保留真实行为 | @SpringSpy |
灵活覆盖特定方法 |
⚠️ 性能提醒:过度使用@SpringBean
会显著降低测试速度,建议按需选择。完整代码示例可在GitHub查看。