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

Time:03-29

i have a method where i am

 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
       
}

where i am reading if a file at path /build/projects.json exists or not. If exists take that file and convert to json array and if does not exist retrieve the data from sonar and make and file at path /build/projects.json and then read that while.

To write the test case for this i have to always mock that file does not exist at path /build/projects.json and have to return the response with a json array reading the data from path src/test/resources/projects.json which is a dummy data. I have tried below approach

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

Its able to mock the file but i am not not able to read file from path src/test/resources/projects.json for line File file = new File("src/test/resources/projects.json")

if i remove GroovyMock(File, global:true) then i am not able to mock File for file.exists() but able to read file using File file = new File("src/test/resources/projects.json")

How can i mock file at method level in groovy using spock?

CodePudding user response:

As you can control settings.currentDirectory, I would suggest to use the TempDir extension and work with the real file system.

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)

CodePudding user response:

I agree with Leonard in that you should avoid global mocks, if other options exists, which here is the case. Global mocks only work for Groovy code under test, which also limits their use.

OTOH, it is nice to explore what we can do with them, so let us do that. Here is a simplified version of your Groovy(!) class under test:

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

Assuming that we only want to mock exists() and cannot inject the File instance, but all other File operations, such as constructor calls and other method calls should continue to function, we can use a global Groovy spy as follows:

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")
  }
}

See? The resource file can be read, the test passes.

Now what if we want to get even fancier and say that we really just want to return a fake result for exists() if the file instance is really the one for the config file path name in the class under test, i.e. in our example "." ? In that case, we somehow need to get hold of the File instance, because we cannot just say this in the stub closure. But we can get the mock object from the closure delegate, and from there we can fetch the file instance and call other methods like getName() on it in order to determine if it is the instance we want to return a fake result for.

Don't do this at home, kids:

  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")
  }

Please note the additional file.exists() in the expect: block. In the unconditional original stubbing version >> false, this condition would fail. Now it passes.

  • Related