Alrighty team, it’s time for a grab-bag of Grails Controller unit testing fragments to get you started. Most of these have been shamelessly stolen from the Graeme’s Grails.org wiki sample code, and spiced up to suit.
On the testing board today is our LoginController, since it’s pretty low hanging fruit for simple test cases that showcase the common stuff you’re likely to want to do.
Let’s start with the index method, that’s gotta be a snack right?
def index = {redirect(action: 'form')}
Not much to do. Need to handle the redirect()
call, so let’s add some setup/teardown code to tackle that one:
def redirectParams /** Setup metaclass fixtures for mocking. */ void setUp() { redirectParams = [ : ] LoginController.metaClass.redirect = { Map args -> redirectParams = args } } /** Remove metaclass fixtures for mocking. */ void tearDown() { def remove = GroovySystem.metaClassRegistry.&removeMetaClass remove LoginController }
Now that we’ve mocked out the redirect stuff, testing becomes pretty straightforward.
void testIndexRedirect() { LoginController lc = new LoginController() lc.index() assertEquals "form" , redirectParams.action }
Piece of cake. But what about something a little trickier? Something that involves request params, sessions, flash, loggers, all that other stuff? Well let’s enhance our setup routine to cater for the common stuff:
def session def params def redirectParams def flash /** Setup metaclass fixtures for mocking. */ void setUp() { session = [ : ] LoginController.metaClass.getSession = { -> session } params = [ : ] LoginController.metaClass.getParams = { -> params } redirectParams = [ : ] LoginController.metaClass.redirect = { Map args -> redirectParams = args } flash = [ : ] LoginController.metaClass.getFlash = { -> flash } def logger = new Expando( debug: { println it }, info: { println it }, warn: { println it }, error: { println it } ) LoginController.metaClass.getLog = { -> logger } }
Alright, now we’re cookin! We’ve mocked out the logger with some funky Expando-ism, and we’ve created readable/writable maps for the request/session/flash/redirect stuff. This will probably end up in a ControllerTests base class, right… but let’s keep on trucking…
With the trickier stuff in place, let’s try our hand at a logout.. Here’s the method under test:
def logout = { if (session.account) { session.account = null } redirect(uri: "/${params.blog}/") // to the blog they logged out from }
So we’re going to want to prepopulate that session.account
business, but since I’m just using a map for the session mock, that’s going to be a cinch:
void testLogout() { session['account'] = "user" params['blog'] = 'demo' LoginController lc = new LoginController() assertNotNull lc.session.account lc.logout() assertEquals "/demo/", redirectParams.uri assertNull lc.session.account }
Giddy up. We’re a long way toward a fully exercised LoginController, but we’ve missing the Jewel in the Crown - our login() routine.
def login = {LoginCommand cmd -> log.debug "Attempting login" if (cmd.hasErrors()) { redirect(action: 'form') } else { Account account = Account.findByUserIdAndPassword(cmd.userId, cmd.password.encodeAsSha1()) if (account) { session.account = account } else { session.account = null flash.loginError = "Invalid username or password. Please try again" } redirect(uri: "/${params.blog}/") } }
That’s going to require some domain class mocking on Account
, but we’re all getting a little more comfortable with ExpandoMetaClass as we go, so it’s no rocket science:
void testLogin() { String.metaClass.encodeAsSha1 = { -> delegate } params['blog'] = 'demo' Account.metaClass.static.findByUserIdAndPassword = { String userId, String password -> if (userId == 'glen' && password == 'sesame' ) { return [ userId: 'glen' ] } else { return null } } LoginCommand.metaClass.hasErrors = { false } LoginCommand goodUser = new LoginCommand(userId: 'glen', password: 'sesame') LoginController lc = new LoginController() lc.login(goodUser) assertEquals "glen", session.account.userId assertEquals "/demo/", redirectParams.uri assertNull flash.loginError LoginCommand badUser = new LoginCommand(userId: 'glen', password: 'unlucky') lc.login(badUser) assertNull session.account assertNotNull flash.loginError }
Ok… Tiny bit of rocket science at the start where I mess with metaClass on String to add my SHA1 codec stuff. But other than that, just creating standard Command and Controller objects and setting/check values in my mocked session/request/etc. Really that testLogin() should be split into testGoodLogin() and testBadLogin(), but I’m rolling into one here just for ease of explanation. You can view the final class on google code.
Well that’s a good lot of Controller testing tricks ofr just one day. If you’ve got your own ideas, feel free to add them here. Next up we’ll turn our hand to services, but I reckon they’ll be more straightforward than this…
Happy Testing!