Grails Tests with Cucumber Cont'd

My previous blog post has been about introducing Cucumber in a Grails application. This post is a follow up on a particular requirement that came up in the course of writing the first Cucumber specifications.

One point of criticism was the lack of auto-complete support for Geb methods and variables which are available in our Cucumber step implementations. As mentioned, the Geb BindingUpdater adds a browser variable and multiple methods to be used in the Cucumber step implementation code. For example, calling browser.go link can be shortened to:

package steps

import pages.admin.LoginPage
import static cucumber.api.groovy.EN.*

Given(~'^a user opens the browser$') {->

}
When(~'^she opens the "([^"]*)" link$') { String link ->
   go link       // instead of browser.go link    
}
Then(~'^she will get a login page$') {->
    
}

As the project team is entirely using IntelliJ, I decided to write a GDSL file. GDSLs can be used to add auto-completion support to Groovy DSLs in IntelliJ - exactly what we needed.

Thanks to Peter Gromov from JetBrains I could come up with a GDSL that even has support for generic method type parameters:

/**
 * Enables code completion for Geb inside Cucumber step implementations.
 */
def forwardedMethods = [
  "go", "to", "via", "at", "waitFor",
  "withAlert", "withNoAlert", "withConfirm", "withNoConfirm",
  "download", "downloadStream", "downloadText", "downloadBytes", "downloadContent", "report", "reportGroup", "cleanReportGroupDir"]

def scriptContext = context(
  filetypes: ['.groovy'], 
  pathRegexp: ".*/test/cucumber/.*", 
  scope: closureScope())

contributor(scriptContext) {
    property name: 'browser', type: 'geb.Browser'

    def methods = findClass('geb.Browser').methods
    methods.findAll { it.name in forwardedMethods }.each { def browserMethod ->

        def params = [:]
        browserMethod.parameterList.parameters.each { param ->
            params.put(
              param.name, 
              com.intellij.psi.util.TypeConversionUtil.erasure(param.type).canonicalText)
        }

        method name: browserMethod.name, type: com.intellij.psi.util.TypeConversionUtil.erasure(browserMethod.returnType).canonicalText, params: params
    }
}

The GDSL registers all methods which are bound by the BindingUpdater plus it adds the browser variable of type geb.Browser for all Groovy script files in the test/cucumber/ directory. TypeConversionUtil is used to type erase the method result and parameter types, so for example <T extends Page> T at(Class<T> pageClass) will be erased to Page at(Class pageClass). One little attack against the DRY principle is the hard-coded list of forwarded methods, that’s something that could be improved with a little IntelliJ PSI kung-fu.

After throwing this as CucumberGebSupport.gdsl in the test/cucumber folder (which is being registered as test-source folder), IntelliJ will recognize the file and enable the auto-completion support as specified.

IntelliJ Auto-Completion for Geb in Cucumber

Conclusion

IntelliJ comes with a neat mechanism that allows to add auto-completion support for custom Groovy DSLs. So-called GDSL files can be used to specify delegation classes, introduce variables and do other fancy stuff that needs to be done to have auto-completion for Groovy DSLs.