JenkinsPipelineUnit icon indicating copy to clipboard operation
JenkinsPipelineUnit copied to clipboard

Feature request: mock global var methods

Open thorntonrose opened this issue 5 years ago • 8 comments

Please add the ability to mock methods of global vars with registerMethod. For example, given vars/foo.groovy:

def call() { ... }
def bar() { ... }

Provide a way to mock foo.bar().

thorntonrose avatar Mar 18 '19 12:03 thorntonrose

Hi, Have you a solution for this issue ?

Thank's

mblanloeil avatar Apr 03 '19 12:04 mblanloeil

Actually,

You can mock bar, but only if it the calling scope is within a different file. (using registerAllowedMethod('bar',[], null).

It doesn't work when you call the function within the same file as they are contained within the same class and the metaClass.invokeMethod nor the metaClass.static.invokeMethod functions are called when calling a different function from the same class. (I think it might be related to initializing PogoMetaMethodSite vs PogoMetaClassSite somewhere deep in the groovy core)

I have no clue how to fix this, but it would be very nice to be able to mock these functions.

Thanks

franknarf8 avatar Sep 06 '19 00:09 franknarf8

You can use an Expando as a workaround for this. Something like any of these:

binding.setVariable('bar', new Expando (foo: "foo result")
binding.setVariable('bar', new Expando (foo: "foo result", baz: "baz result")
binding.setVariable('bar', new Expando (foo: { return calculate_foo_result () })
etc.

idij avatar Sep 06 '19 09:09 idij

I think my explanation might have been a bit confusing, I'll retry to explain with an example:

scriptA.groovy

call(Map args) {
    print()
}

void print() {
    echo('hello')
}

scriptB.groovy

call(Map args) {
    scriptA.echo('hello')
}

When testing scriptB, I'm doing something of the sort, and I am able to successfully mock print

binding.setVariable('scriptA', loadScript('vars/scriptA.groovy'))
helper.registerAllowedMethod('print', []) { -> } // <- mock gets called successfully
loadScript('vars/scriptB.groovy').call()

But, when testing scriptA, it is not possible to mock print

helper.registerAllowedMethod('print', []) { -> } // <- mock never gets called
loadScript('vars/scriptA.groovy').call()

franknarf8 avatar Sep 10 '19 16:09 franknarf8

Duplicates #51 #142 #141

stchar avatar Dec 06 '19 10:12 stchar

helper.registerAllowedMethod('print', []) { -> } // <- mock never gets called

Try

    helper.registerAllowedMethod('print', [], { null })

I mocked a lot of global methods and worked pretty nicely. I tested the above approach and worked as aspected. You can even add some codes in the null section and put breakpoints into it to verify.

    helper.registerAllowedMethod('print', [], {
        println 'Put a breakpoint here and see it breaks.'
    })

zionyx avatar Dec 21 '19 08:12 zionyx

I'm trying to make something like this work, but without success so far... I have a shared library with several vars/*****.groovy files exposing different functions. I'm using them from my Jenkinsfile using xxxxx.function(...) and yyyyy.function(...) where xxxxx and yyyyy are the names of those vars/*****.groovy files.

I need to mock all the functions in one of the *****.groovy files as they are making calls to externals systems that are not available while testing. Something like helper.registerAllowedMethod('xxxxx.function', ....) would be perfect, but it doesn¡t work, and I'm not able to find an alternative using other mocking frameworks as I don't know how to refer to the class I need to mock.

Any hint would be appreciated. Thanks!

jgsogo avatar May 19 '20 15:05 jgsogo

helper.registerAllowedMethod('xxxxx.function', ....) won't work because it only registers a single top level method. For your use case, you must mock the class xxxxx.

You can then set class xxxxx as property xxxxx so your shared library can use them in the tests as xxxxx.function() calls.

        binding.setProperty('xxxxx', new xxxxx())

An example of a mocked class:

package mock // up to you

class PipelineHelper implements Serializable {

    public void requiredParameterCheck(def paramName, def paramValue, def errorMessage = '') {
        if (errorMessage == '') {
            errorMessage = "--- SHARED LIBRARY ERROR: Missing required parameter ${paramName} ---"
        }
        if ((paramValue == null) || (paramValue == '')) {
            throw new Exception(errorMessage)
        }
    }
}

This mocked class is a partial copy of my pipelineHelper.groovy. The requiredParameterCheck() method is commonly used in other shared pipeline libraries. Notice that i do not mock def call() {...} of pipelineHelper.groovy because it is not in the scope for mocking.

Depending on the complexity of the method. If you use steps like echo in the method above, you need to register echo in your test file with:

        helper.registerAllowedMethod('echo', [String.class], {
            null // or do something with it
        })

My shared pipeline library is unfortunately not open source yet, but I work towards open sourcing it. Hope that helps.

zionyx avatar May 20 '20 10:05 zionyx