Search code examples
groovynullpointerexceptionspockspy

groovy spock testing closure with spy


I have shared library that calls pipeline step method(withCredentials).I am trying to test withCredentials method is being called correctly with sh scripts on calling myMethodToTest but facing error while iterating in withCredentials closure:

Method To Test

 class myClass implements Serializable{
    def steps
    public myClass(steps) {this.steps = steps}

    public void myMethodToTest(script, credentialsId, dataObject) {
    dataObject.myKeyValue.each {
        steps.withCredentials([[
           $class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
           usernameVariable: 'USR', passwordVariable: 'PWD']]) {
             steps.sh("git push --set-upstream origin ${it.branch}")
           }
      }
   }      
}

Mocking

class Steps {
   def withCredentials(List args, Closure closure) {}
}

class Script {
    public Map env = [:]
}

Test case

def "testMyMethod"(){
        given:
        def steps = Spy(Steps)
        def script = Mock(Script)
        def myClassObj = new myClass(steps)
        def myDataObject = [
          'myKeyValue' : [['branch' :'mock' ]]
        ]

        when:
        def result = myClassObj.myMethodToTest(script, credId, myDataObject)

        then:
        1 * steps.withCredentials([[
            $class: 'UsernamePasswordMultiBinding', 
            credentialsId: "mycredId", 
            usernameVariable: 'USR', 
            passwordVariable: 'PWD'
        ]])  
        1 * steps.sh(shString)

        where:
        credId     | shString
        "mycredId" | "git push --set-upstream origin mock"

Error (it variable becomes null in closure)

java.lang.NullPointerException: Cannot get property 'branch' on null object

Solution

  • You have a case of two nested closures

    dataObject.myKeyValue.each { // <- first closure it references the map
        steps.withCredentials([[
           $class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
           usernameVariable: 'USR', passwordVariable: 'PWD']]) { // <- second closure it is null as no parameter is passed to this closure
             steps.sh("git push --set-upstream origin ${it.branch}")
        }
    }
    

    To fix it you should name the first parameter

    dataObject.myKeyValue.each { conf ->
        steps.withCredentials([[
           $class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
           usernameVariable: 'USR', passwordVariable: 'PWD']]) {
             steps.sh("git push --set-upstream origin ${conf.branch}")
        }
    }