Inject Stub Collaborators In Globally Stubbed Class Using Spock and Grails

846 Views Asked by At

I'm rendering a GSP in a Spock unit test with Grails 2.4.4. The GSP has a number of custom taglibs, and many of them call external services. The problem is that I can't inject a stubbed service into those taglibs, and when I call the service, it's always null. Here's what I'm trying to get simplest case working:

@TestMixin(GroovyPageUnitTestMixin)
@Mock(FooTagLib)
class MyGspSpec extends Specification {
    def setup() {
        def barStub = Stub(BarService)
        def tagLibStub = GroovyStub(FooTagLib, global:true) {

        }
        tagLibStub.barService = barStub
    }
    def 'test method'() {
        when: String result = render('myView', model:[:])
        then: assert result != null
    }
}

Taglib:

class FooTagLib {
    static String namespace = "baz"
    BarService barService
    Closure<StreamCharBuffer> foo = { GroovyPageAttibutess attrs ->
        out << barService.something()
    }
}

_foo.gsp contents:

<baz:foo/>

I've also tried this:

FooTagLib.metaClass.barService = Stub(BarService) //also tried GroovyStub

I even tried putting a getter on the taglib and stubbing that, but it didn't work either:

In setup:

def barService = Stub(BarService)
GroovyStub(FooTagLib, global:true) {
    getBarService() >> barService
}

In taglib:

BarService barService
BarService getBarService() { return barService }
//....
out << getBarService().something()

Finally, the only thing that worked (with the getter) is this:

FooTagLib.metaClass.getBarService = { -> return Stub(BarService) }

But this seems like a really bad hack.

I'm not sure how I can inject a stubbed version of it into the taglib. In my mind at least one of these should work, but obviously I'm doing something wrong.

1

There are 1 best solutions below

0
On BEST ANSWER

This is how I would pass unit specs for the tagLib:

@TestFor(FooTagLib)
class FooTagLibSpec extends Specification {

    def setup() {
        // tagLib is available by default of above annotation is used
        tagLib.barService = Mock(BarService) {
            // Mock the service and stub the response
            1 * serviceMethod() >> { "Hello World Returns" }
        }
    }

    void "test something"() {
        expect: 'applying template would result in mocked response'
        applyTemplate('<baz:foo/>') == "Hello World Returns"
    }

    void "test taglib method call"() {
        expect:
        tagLib.foo() == "Hello World Returns"
    }
}

class FooTagLib {
    static defaultEncodeAs = [taglib:'html']
    static String namespace = "baz"    
    BarService barService

    Closure<StreamCharBuffer> foo = { attrs ->
        out << barService.serviceMethod()
    }
}

class BarService {
    def serviceMethod() { "Hello World" }
}

I hope I was able to meet the checkpoints you were looking for in your tests. You can also see spec is able to mock BarService without issues. Give a shout if you need for info.