我如何在 spock 的方法级别模拟 new File()?

How can i mock new File() at method level in spock?

我有一个方法

 JSONArray execute(SonarqubeMaintenanceSetting settings) {
     String projectsFilePath = "${settings.currentDirectory}/build/projects.json"
        File targetDir = new File(projectsFilePath)
         if (!targetDir.exists()) {
 String url = SONAR_API_URL + 'projects/search?ps=500'
            Object response = getProjectList(settings.sonarToken, url)

            Object[] responseArr = []
            if (response != null && response.components != null && response.paging.total != null)   {
                responseArr = response.components
                
            }

            JSONArray projectList = new JSONArray(responseArr)
            byte[] bytes = projectList.toString().bytes
            OutputStream out = new FileOutputStream(projectsFilePath)
            out.write(bytes)
        }
        InputStreamReader inputStreamReader = new InputStreamReader(new 
        File(projectsFilePath), 'UTF-8')
        BufferedReader reader = new BufferedReader(inputStreamReader)
        Object obj = jsonSlurper.parse(reader)
        JSONArray projectListInFile = new JSONArray(obj)
        projectListInFile
       
}

如果路径 /build/projects.json 中的文件存在与否,我正在读取的位置。如果存在,则获取该文件并转换为 json 数组,如果不存在,则从声纳中检索数据,并在路径 /build/projects.json 处生成和文件,然后读取该文件。

要为此编写测试用例,我必须始终模拟路径 /build/projects.json 中不存在的文件,并且必须 return 使用 [=34] 进行响应=] 数组从路径 src/test/resources/projects.json 读取数据,这是一个虚拟数据。我试过下面的方法

class SonarqubeMaintenanceMainTestSpec extends Specification {

    @Subject
    SonarqubeMaintenanceMain subject
    SonarqubeMaintenanceMain instance

    def setup() {
        subject = new SonarqubeMaintenanceMain()
        subject.with {
            instance = Mock(SonarqubeMaintenanceMain)
        }
        instance = subject.instance
        GroovyMock(File, global:true)

    }

    void "Test execute with settings"() {
        given:
        File dir = Mock()
        File file = new File("src/test/resources/projects.json") // this will be read as null if GroovyMock(File, global:true) is set 
        
        when:
        JSONArray listOfProject = subject.execute(settings)

        then:
        1 * new File("${settings.currentDirectory}/build/projects.json") >> dir
        1 * mockFile.exists() >> false
        1 * new File("${settings.currentDirectory}/build/projects.json") >> file

它能够模拟文件,但我无法从路径 src/test/resources/projects.json 读取文件的行 File file = new File("src/test/resources/projects.json")

如果我删除 GroovyMock(File, global:true) 然后我无法为 file.exists() 模拟文件,但能够使用 File file = new File("src/test/resources/projects.json")

读取文件

如何在 groovy 中使用 spock 在方法级别模拟文件?

由于您可以控制 settings.currentDirectory,我建议使用 TempDir 扩展并使用真实文件系统。

class SonarqubeMaintenanceMainTestSpec extends Specification {
    @TempDir
    File settingsDir

 void "Test execute with settings"() {
        given:
        def projectJson = new File(settingsDir, 'build/projects.json').tap { parent.mkdirs() }
        projectJson.text = SonarqubeMaintenanceMainTestSpec.getResource('/projects.json').text
        
        and:
        def settings = new Settings(currentDirectory: settingsDir.absolutePath)

        when:
        JSONArray listOfProject = subject.execute(settings)

我同意 Leonard 的观点,如果存在其他选项,您应该避免全局模拟,这里就是这种情况。全局模拟仅适用于 Groovy 被测代码,这也限制了它们的使用。

OTOH,很高兴探索我们可以用它们做什么,所以让我们去做吧。这是您正在测试的 Groovy(!)class 的简化版本:

class UnderTest {
  boolean isFound() {
    // Current directory should always exist -> true
    new File(".").exists()
  }
}

假设我们只想模拟 exists() 而不能注入 File 实例,但是所有其他 File 操作,例如构造函数调用和其他方法调用应该继续运行,我们可以使用全局 Groovy 间谍如下:

import spock.lang.Specification
import spock.lang.Subject

class SonarqubeMaintenanceMainTest extends Specification {
  @Subject
  UnderTest subject

  def setup() {
    subject = new UnderTest()
    GroovySpy(File, constructorArgs: ["x"], global: true) {
      exists() >> false
    }
  }

  void "Test execute with settings"() {
    given:
    File file = new File("src/test/resources/test.txt")

    expect:
    !subject.isFound()
    file.text.startsWith("Lorem ipsum")
  }
}

看到了吗?可以读取资源文件,测试通过

现在,如果我们想变得更有趣,说我们真的只想 return exists() 的假结果,如果文件实例确实是配置文件路径名的那个在被测 class 中,即在我们的示例中 "." ?在那种情况下,我们需要以某种方式获取 File 实例,因为我们不能只在存根闭包中说 this 。但是我们可以从闭包委托中获取 mock object,从那里我们可以获取文件实例并调用其他方法,例如 getName() 以确定它是否是我们想要的实例 return 的假结果。

孩子们,不要在家里这样做:

  def setup() {
    subject = new UnderTest()
    GroovySpy(File, constructorArgs: ["x"], global: true) {
      exists() >> {
        delegate.mockObject.instance.name == "." ? false : callRealMethod()
      }
    }
  }

  void "Test execute with settings"() {
    given:
    File file = new File("src/test/resources/test.txt")

    expect:
    !subject.isFound()
    file.exists()
    file.text.startsWith("Lorem ipsum")
  }

请注意 expect: 块中的附加 file.exists()。在无条件原始存根版本 >> false 中,此条件会失败。现在它通过了。