如何用Spock模拟new URL(url).openStream()返回file.gz文件?
在Spock中模拟URL.openStream()返回gzip文件的解决方案
当然可以!我来帮你搞定在Spock框架里模拟new URL(url).openStream()返回gzip文件的需求。针对你的DownloadFile类,我提供两种可行方案,优先推荐无需额外依赖的轻量方案,也会覆盖无法修改原代码的场景。
方案一:抽离URL构造逻辑(推荐,无需PowerMock)
你的代码里直接new URL(url)创建实例,导致难以Mock。我们可以把URL的创建抽成一个可覆盖的方法,这样用Spock的Spy就能轻松替换成Mock对象。
第一步:修改原代码(微小调整)
在DownloadFile类中新增一个protected方法负责创建URL,同时把System.exit(0)也抽成方法(避免测试时JVM退出):
public class DownloadFile { public BufferedReader downloadGzipCsvFile(String url) throws MalformedURLException { BufferedReader br = null; if (UrlValidator.getInstance().isValid(url)){ try { // 替换直接new URL(url)为调用createUrl方法 br = new BufferedReader(new InputStreamReader(new GZIPInputStream(createUrl(url).openStream()))); } catch (IOException e) { e.printStackTrace(); // 抽离退出逻辑,方便测试Mock handleExit(); } } else { // 原else逻辑 } return br; } // 新增:抽离URL构造逻辑 protected URL createUrl(String url) throws MalformedURLException { return new URL(url); } // 新增:抽离退出逻辑 protected void handleExit() { System.exit(0); } }
第二步:编写Spock测试用例
用Spy监听DownloadFile,替换createUrl返回Mock的URL,同时让Mock URL的openStream()返回本地测试用的gzip文件流:
class DownloadFileSpec extends Specification { def "downloadGzipCsvFile should return valid BufferedReader from gzip stream"() { given: "准备测试依赖:Spy对象、Mock URL、测试用gzip文件流" def downloadFile = Spy(DownloadFile) // 使用Spy覆盖protected方法 def testGzipStream = getClass().getResourceAsStream("/test.csv.gz") // 从resources读取测试gzip def mockUrl = Mock(URL) // 让Spy的createUrl方法返回Mock URL downloadFile.createUrl(_) >> mockUrl // 禁止测试时调用System.exit,避免JVM退出 downloadFile.handleExit() >> {} when: "调用下载方法" def reader = downloadFile.downloadGzipCsvFile("http://example.com/valid/file.gz") then: "验证Mock交互和结果" // 确认Mock URL的openStream被调用,返回测试gzip流 1 * mockUrl.openStream() >> testGzipStream // 验证Reader非空,可额外验证读取内容是否符合预期 reader != null reader.readLine() == "expected_column1,expected_column2" // 替换成你测试gzip里的内容 } }
方案二:用PowerMock直接Mock URL构造(无需修改原代码)
如果无法调整原代码,我们可以用PowerMock拦截URL的构造方法,强制返回Mock对象。需要额外引入PowerMock依赖。
第一步:添加PowerMock依赖(以Gradle为例)
testImplementation "org.powermock:powermock-module-junit4:2.0.9" testImplementation "org.powermock:powermock-api-mockito2:2.0.9" testImplementation "org.spockframework:spock-core:2.0-groovy-3.0"
第二步:编写PowerMock+Spock测试用例
import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import org.powermock.modules.junit4.PowerMockRunnerDelegate import org.spockframework.runtime.Sputnik import spock.lang.Specification @RunWith(PowerMockRunner) @PowerMockRunnerDelegate(Sputnik) // 让PowerMock兼容Spock @PrepareForTest(URL.class) // 告诉PowerMock要处理URL类 class DownloadFilePowerMockSpec extends Specification { def "downloadGzipCsvFile mocks URL.openStream() directly"() { given: "准备测试gzip流和Mock URL" def downloadFile = new DownloadFile() def testGzipStream = getClass().getResourceAsStream("/test.csv.gz") def mockUrl = Mock(URL) // 拦截URL构造方法,传入目标URL时返回Mock对象 PowerMockito.whenNew(URL.class).withArguments("http://example.com/valid/file.gz").thenReturn(mockUrl) // 让Mock URL的openStream返回测试gzip流 mockUrl.openStream() >> testGzipStream when: "调用下载方法" def reader = downloadFile.downloadGzipCsvFile("http://example.com/valid/file.gz") then: "验证结果有效" reader != null reader.readLine() == "expected_column1,expected_column2" } }
关键注意事项
- 把测试用的
test.csv.gz放在src/test/resources目录下,确保能被getResourceAsStream读取到。 - 原代码中的
System.exit(0)会导致测试时JVM直接退出,方案一中抽离成handleExit方法后,测试时Mock掉即可避免这个问题。
内容的提问来源于stack exchange,提问作者Droid_Interceptor
相关产品推荐
相关产品推荐

