Spock单元测试中如何Mock new FileInputStream构造方法
两种写法的问题说明
- 第一种Stub写法的错误:
你对Spock的Stub配置语法理解有误。Stub(Class){}闭包是用来配置当前Stub实例自身的方法调用返回规则的,不支持在里面写构造方法拦截逻辑。你只是创建了一个孤立的FileInputStream Stub对象,代码里实际执行new FileInputStream(gcpKey)时还是会走真实的构造逻辑去读磁盘文件,和你创建的Stub没有任何关联,自然无法生效。 - 第二种GroovySpy写法的错误:
两个核心问题:一是没有加global: true参数,默认的GroovySpy只会代理你显式创建的那个spy实例,不会拦截代码中全局范围的对象实例化操作;二是name: "name"只是给spy实例设置一个用于日志输出的名称,和构造方法的参数匹配没有任何关系,你写的1 * new FileInputStream("name")只会匹配传入参数恰好是字符串"name"的构造调用,和实际传入的gcpKey值不匹配的话也不会触发拦截。
正确实现方案
优先推荐无侵入的实现方式,尽量避免mock JDK核心类的构造方法,减少测试副作用。
方案1:使用临时测试文件(最推荐)
不需要mock FileInputStream,用Spock自带的临时目录能力创建假的密钥文件,让代码走真实的文件读取逻辑,和生产运行逻辑完全一致,稳定性最高:
import spock.lang.TempDir class YourClassSpec extends Specification { @TempDir File tempDir def "getCredentials should return valid credentials"() { given: "准备测试用临时密钥文件" File testKey = new File(tempDir, "gcp-test-key.json") // 写入符合GCP密钥格式的测试内容 testKey.write """{ "type": "service_account", "project_id": "test-project" }""" YourClass yourClass = new YourClass() yourClass.gcpKey = testKey.absolutePath GoogleCredentials mockCred = Mock() try (def staticMock = MockStatic(GoogleCredentials.class)) { staticMock.when(() -> GoogleCredentials.fromStream(_ as InputStream)) .thenReturn(mockCred) when: "调用getCredentials方法" Credentials result = yourClass.getCredentials() then: "返回正确的凭证" result == mockCred } } }
方案2:重构代码解耦依赖
把流创建逻辑和凭证加载逻辑拆分,从根本上避免需要mock构造方法的场景:
// 拆分出可测试的核心逻辑,直接接收InputStream参数 protected Credentials getCredentials(InputStream keyStream) throws IOException { return GoogleCredentials.fromStream(keyStream); } // 外层方法只负责文件流的创建 private Credentials getCredentials() throws IOException { try (FileInputStream stream = new FileInputStream(gcpKey)) { return getCredentials(stream); } }
测试时直接传入mock的InputStream即可,不需要处理文件构造逻辑:
def "getCredentials with input stream should return valid cred"() { given: InputStream mockStream = Mock() GoogleCredentials mockCred = Mock() YourClass yourClass = new YourClass() try (def staticMock = MockStatic(GoogleCredentials.class)) { staticMock.when(() -> GoogleCredentials.fromStream(mockStream)) .thenReturn(mockCred) when: Credentials result = yourClass.getCredentials(mockStream) then: result == mockCred } }
方案3:全局GroovySpy拦截构造(不推荐,仅用于无法重构的遗留代码)
如果暂时无法重构代码,必须mock构造方法,需要正确配置全局GroovySpy:
def "getCredentials with global constructor mock"() { given: InputStream mockStream = Mock() GoogleCredentials mockCred = Mock() // 必须加global: true开启全局构造拦截 GroovySpy(FileInputStream, global: true) // 拦截所有String参数的FileInputStream构造,返回mock流 new FileInputStream(_ as String) >> mockStream YourClass yourClass = new YourClass() yourClass.gcpKey = "test-key-path" try (def staticMock = MockStatic(GoogleCredentials.class)) { staticMock.when(() -> GoogleCredentials.fromStream(mockStream)) .thenReturn(mockCred) when: Credentials result = yourClass.getCredentials() then: result == mockCred // 验证构造方法确实被正确参数调用 1 * new FileInputStream("test-key-path") } }
注意:全局GroovySpy会拦截当前测试线程中所有FileInputStream的实例化,测试完成后会自动还原,不要在并发测试场景使用该方案,避免测试用例之间互相干扰。
内容的提问来源于stack exchange,提问作者sndu
相关产品推荐
相关产品推荐

