1. 引言

在使用Spock测试Spring应用时,我们经常需要替换Spring管理的组件行为。本文将介绍如何用自定义的StubMockSpy替换Spring自动注入的依赖。虽然示例主要使用Spock的Stub,但这些技术同样适用于MockSpy

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),不能用defObject

完整测试示例:

@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查看。


原始标题:Injecting a Mock as a Spring Bean in a Spock Spring Test | Baeldung