Mocking with Groovy

grzegorzgrzegorz

grzegorzgrzegorz

Posted on April 14, 2024

Mocking with Groovy

I would like to share some nice feature I am using extensively to test Groovy code. I am going to make this post short and hopefully useful.

Mocking in Groovy

There are tons of possiblities to mock in Groovy: groovy metaprogramming. Groovy metaprogramming is one of the features which stands out for sure. We are talking about runtime and compile time one. There are others features like: ability to use static and dynamic types, ability to use functional and oo paradigm and also almost full Java compatiblity so you can write in Java if you need for some reason. Groovy is JVM language, so it works wherever JVM works: if you never tried you should at least take a look. It's been years I have been using it and I am still impressed. Ok, I really want to make it short...

StubFor/MockFor example

So, to selectively mock a method it seems one of the most popular way to do it (aside ExpandoMetaClass) is to use mockFor/stubFor classes.
Given is the class under test:

class ClassUnderTest {

    def start(){
        println "starting"
        inner()
    }

    def inner(){
        return "original start message"
    }

    def stop(){
        println "stopping"
        return "original stop message"
    }
}
Enter fullscreen mode Exit fullscreen mode

When inner method is to be mocked to return modified return value, we can try to test it like this:

void "test: stubFor use"() {
        given:
        def stub = new StubFor(ClassUnderTest)
        stub.demand.with {
            inner{ return "mocked start" }
        }
        stub.ignore("start")
        stub.ignore("stop")
        when:
        stub.use {
            def classUnderTest = new ClassUnderTest()
            def startResult = classUnderTest.start()
            def stopResult = classUnderTest.stop()
            then:
            assert startResult == "mocked start"
            assert stopResult == "original stop message"
        }
    }
Enter fullscreen mode Exit fullscreen mode

And... it doesn't work. I put inner method on purpose in ClassUnderTest as it cannot be mocked in this way. The above test fails:

Assertion failed: 

assert startResult == "mocked start"
       |           |
       |           false
       'original start message'
Enter fullscreen mode Exit fullscreen mode

It happens so as when ignoring specific method, all underlying calls seem to be ignored as well. I stumbled upon this so many times I needed another solution.

ProxyMetaClass

ProxyMetaClass extends MetaClassImpl so it is a sibling for ExpandoMetaClass. I put the link for Expando but not for ProxyMetaClass as for some reason there is no decent documentation for it.
Anyway, it turns out this is very handy way for mocking things in Groovy, which works as needed:

void "test: ProxyMetaClass use"() {
        given:
        ProxyMetaClass clsUnderTestSpy = ProxyMetaClass.getInstance(ClassUnderTest)
        clsUnderTestSpy.interceptor = new GroovySpy([inner: "mocked start"])
        when:
        clsUnderTestSpy.use {
            def classUnderTest = new ClassUnderTest()
            def startResult = classUnderTest.start()
            def stopResult = classUnderTest.stop()
            then:
            assert startResult == "mocked start"
            assert stopResult == "original stop message"
        }
    }
Enter fullscreen mode Exit fullscreen mode

With GroovySpy class:

class GroovySpy implements Interceptor {

    Map mockMap = [:]
    boolean invokeMethod = true

    GroovySpy(map) {
        mockMap = map
    }

    Object beforeInvoke(Object obj, String methodName, Object[] args) {
        if (mockMap.any { entry -> entry.key == methodName }) {
            invokeMethod = false // do not invoke if method is mocked
        }
    }

    boolean doInvoke() {
        return invokeMethod
    }

    Object afterInvoke(Object obj, String methodName, Object[] args, Object res) {
        invokeMethod = true // restore variable value
        if (mockMap.any { entry -> entry.key == methodName }) {
            return mockMap[methodName] // return mocked value
        }
        return res // or return original result if method is not mocked
    }
}
Enter fullscreen mode Exit fullscreen mode

We can provide multiple methods in a map and they will be mocked while the others will be run as originals.

There is huge amount of information about ExpandoMetaClass around so I do not put anything related here. Also, I authored Jenkins testing framework where I extensively use ExpandoMetaClass to mock everything in pipeline file (which is beyond default Groovy syntax) so you could take a look as well: Jenkinson

Summary

Groovy is extremely flexible and handy in most of the situations. ProxyMetaClass is great way for mocking but lacks documentation or even useful examples that is why I think above information should be useful one. At least it helped me a lot in my tasks.

💖 💪 🙅 🚩
grzegorzgrzegorz
grzegorzgrzegorz

Posted on April 14, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Mocking with Groovy
groovy Mocking with Groovy

April 14, 2024