Search code examples
groovyspockgeb

How to get a Geb module instance with its declared class?


Up to Geb version 0.10 the example code below has worked just fine:

package whatever

import geb.Module
import geb.Page
import geb.spock.GebSpec

class ExampleSpec extends GebSpec {

    def 'MODULE - Y U NO HAVE THE RIGHT CLASS?'() {

        when:
            ExamplePage page = to ExamplePage

        then:
            verifySomething(page.theModule)
    }

    boolean verifySomething(ExampleModule module) {
        // ...
    }
}

class ExamplePage extends Page {

    static content = {
        theModule { module ExampleModule }
    }
}

class ExampleModule extends Module {

}

I wanted upgrade to the latest 0.13.1 but apparently the breaking (regressive I would say) change has been introduced which results with:

groovy.lang.MissingMethodException: No signature of method: geb.navigator.NonEmptyNavigator.verifySomething() is applicable for argument types: (geb.content.TemplateDerivedPageContent) values: [whatever.ExamplePage -> theModule: whatever.ExampleModule]

I've noticed that the same happens but with different class since version 0.11, the exception message is as follows:

groovy.lang.MissingMethodException: No signature of method: geb.navigator.NonEmptyNavigator.verifySomething() is applicable for argument types: (geb.content.SimplePageContent) values: [theModule - SimplePageContent (owner: whatever.ExamplePage, args: [], value: null)]

Why module declared with a given, specific class looses it at runtime? How to prevent that?


Solution

  • Objects implementing Navigator interface (which includes classes extending from Module) and returned from content definitions are wrapped with TemplateDerivedPageContent objects which delegate to the underlying object but also allow to produce a meaningful path to the object for error reporting.

    The wrapping of modules worked in older versions of Geb, then it got inadvertently removed and now it's back. Even though you can still call all the methods of the module when it's wrapped thanks to TemplateDerivedPageContent dynamically delegating to the underlying object you run into trouble in cases like yours - when you want to strongly type your code that uses modules. Therefore I'm still undecided what we should sacrifice here - better error reporting or ability to strongly type and this wrapping might be removed in a future version of Geb.

    Luckily there is a workaround - if you want to strongly type code that consumes modules then use a getter instead of a content definition to declare them. In your case it would be:

    class ExamplePage extends Page {
    
        ExampleModule getTheModule() {
            module ExampleModule
        }
    
    }