Friday, November 13, 2009

Injecting resources in easyb Grails stories

Note: the functionality described in this post and the which the plugin described below provides have been incorporated into the latest version of the easyb Grails plugin (plugin version 1.2). I'll leave this post here for historical context.

Normally, when you're writing Grails tests, you can have Grails inject things like service instances or the Grails application itself by simply def-ing them in your test case:

class MyTests {
def grailsApplication
def someService
}


However, when you're working with with the easyb Grails plugin, the stories and specifications don't go through the same process as the standard tests, so you can't just def something at the beginning of the story and access it in the scenarios.

Instead, you have to access things through the Grails ApplicationHolder, a class that exists only to provide access to the GrailsApplication instance. Once you have the application instance (and through that, the main Spring context), you can use the getBean() method to reference things that you'd normally have injected:

import org.codehaus.groovy.grails.commons.ApplicationHolder
. . .
ApplicationHolder.application.mainContext.getBean("someService")


Putting that all together with a story might look something like this:

import org.codehaus.groovy.grails.commons.ApplicationHolder

scenario "Grails App injection", {

given "service instances initialized from Grails ApplicationHolder", {
grailsApplication = ApplicationHolder.application
someService = ApplicationHolder.application.mainContext.getBean("someService")
}
then "the Grails application should not be null", {
grailsApplication.shouldNotBe null
}
and "the service instance should not be null", {
someService.shouldNotBe null
}

}


While that's not too bad, it's actually a good bit more noise than I'd like, especially since I can see doing this often in my Grails integration tests. To make it a little easier to read, I ended up writing an easyb plugin to make it look like I was actually injecting things in the easyb stories (I wrote up the process I used here . . . there's also a good wiki entry about the Plugin API at the easyb site). With the plugin I wrote (jar file available here under the MIT license, though it's subject to change as I learn more about easyb . . . the source will eventually be available if people find it useful), I can write a story with easyb's "using" syntax to include the plugin and the custom "inject" keyword:

using "grails-inject"

inject "grailsApplication"
inject "someService"

scenario "Grails App injection", {

given "Service instances initialized from Grails ApplicationHolder"
then "the Grails application should not be null", {
grailsApplication.shouldNotBe null
}
and "the service instance should not be null", {
someService.shouldNotBe null
}

}


With the instances injected into my tests, I can now call whatever methods I want on them, dealing with them in the same way I would in a standard Grails test case. NOTE: if you want to use the plugin above, download it and drop it in the lib directory of your Grails app. When you run "grails easyb-test", easyb should pick it up and make the syntax available to your stories and specifications.

3 comments:

Andy said...

This is great! The plugin API was put into place to facilitate the easyb grails plugin -- I'm not sure what's already being injected (if anything) so this is super nice!! I can see this plugin being useful for other scenarios as well (in addition to Grails, that is).

Brandon said...

Hey Jeffrey, Finally got around to testing out the plugin. I installed the jar in the lib folder and ran grails dev easyb-test, but i get this failure:

[FAILURE: No signature of method: org.codehaus.groovy.grails.commons.DefaultGrailsApplication.shouldNotBe() is applicable for argument types: (null) values: [null]]

any idea what I'm doing wrong?

p.s. also, im using this to test my own grails plugin, could that cause issues?

Andy said...

Brandon -- where are you using the shouldNotBe call? In what section of your story -- shouldNotBe and its relatives only work in "then" clauses....