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.