使用Spock Mock Spring Repository注解类时返回null的原因探究
这是个挺常见的Spring与Spock整合的小坑,我来帮你拆解问题原因和可行的解决方案:
问题现象回顾
- 当自定义的
TestRepository标注@Repository注解时,通过SpockDetachedMockFactory创建的Mock注入后,调用方法始终返回null - 但将注解替换为
@Component、@Service或@Controller时,Mock能正常返回预期的TestModel实例
核心原因分析
Spring对标注@Repository的Bean有特殊的自动处理逻辑:它会为这类Bean自动生成代理类,主要用于统一转换持久层抛出的异常(比如将JDBC异常转为Spring的DataAccessException)。这个代理会包裹你创建的Spock Mock,导致你设置的方法交互(testRepository.getTestModel >> new TestModel())无法被正确触发,最终返回默认的null。
而@Component、@Service等其他 stereotype 注解不会触发这个自动代理逻辑,所以Mock能正常工作。
可行解决方案
方案1:使用Spock的@SpringBean注解(推荐)
Spock的Spring模块提供了@SpringBean注解,专门用于在Spring上下文里替换目标Bean为Spock Mock,它能自动处理Spring的代理问题,代码更简洁:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) @AutoConfigureMockMvc(secure = false) @DirtiesContext class TestControllerSpec extends Specification { @Autowired MockMvc mockMvc // 直接用@SpringBean定义Mock,无需手动编写@TestConfiguration @SpringBean TestRepository testRepository = Mock() def "test endpoint returns expected model"() { given: // 正常设置Mock交互 testRepository.getTestModel() >> new TestModel() when: def result = mockMvc.perform(get("/test")) then: // 按需添加断言 result.status.isOk() } }
方案2:禁用Repository的自动代理
如果你的测试不需要其他Spring Data Repository的功能,可以通过配置属性关闭Repository的自动代理:
@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = ["spring.data.jpa.repositories.enabled=false"] ) @AutoConfigureMockMvc(secure = false) @DirtiesContext class TestControllerSpec extends Specification { // 保留原有的Mock配置即可 @Autowired MockMvc mockMvc @Autowired TestRepository testRepository def "test"() { testRepository.getTestModel() >> new TestModel() // 测试逻辑... } @TestConfiguration static class MockConfig { def factory = new DetachedMockFactory() @Bean @Primary TestRepository testRepository() { factory.Mock(TestRepository) } } }
注意:这个方法会全局禁用Spring Data Repository的自动配置,如果测试中涉及其他真实Repository,可能会影响功能。
方案3:给自定义Repository添加@NoRepositoryBean
如果你的TestRepository不是真正的Spring Data Repository(只是自定义的持久层工具类),可以添加@NoRepositoryBean告诉Spring不要将其视为持久层Repository,从而避免自动代理:
@Repository @NoRepositoryBean class TestRepository { TestModel getTestModel() { // 原有逻辑 } }
这样原有的Mock配置就能正常生效了。
内容的提问来源于stack exchange,提问作者Patrick Roos

