Friday, February 22, 2008

Integration testing for Grails controllers

I've been negligent in writing integration tests for my controllers, mostly because I ran into some issues and put off figuring it out. So while on a train I wrote a simple 24-line controller with action-closures that used the 5 basic operations most in my applications:

  1. render

  2. rended JSON

  3. return map

  4. redirect

  5. return string


And I threw in a service for good measure, and then wrote integration tests.
Here's a couple of points to remember:

  • When a map is returned, the map is available directly as the return value from the action

  • When JSON is returned, use the controller.response.contentAsString

  • When a String is returned, it is available directly as the return value from the action

  • When the action does a render, use the controller's ModelAndView's model property to test model data. Note that the view did not actually render but you can test the viewName property value. Also note that what you can't seem to be able to do is see how the page would ultimately be rendered.

  • When the action does a redirect, check the url with the controller's MockHttpServletResponse

  • To use a service, the test has to interject the service. Note there may be other things that Grails normally interjects for you that you may have to manually set



Here's the domain:

class Person {
String firstName, lastName, email
String toString() {"$id $firstName $lastName <$email>" }
}

Here's the controller:

class PersonController {
def list = {
[ personList: Person.list( params ) ]
}
def showWithReturnMap = {
def person = Person.get( params.id )
if(!person) {
flash.message = "Person not found with id ${params.id}"
redirect(action:list)
}
else { return [ person : person ] }
}
def showWithReturnString = {
return Person.get( params.id ).toString()
}
def showWithRender = {
render view:'show', model:[ person : Person.get( params.id ) ]
}
def showWithJSON = {
render Person.get( params.id ) as JSON
}

MathService mathService
def usesService = {
return mathService.add(params.a, params.b).toString()
}
}


And here's the integration test:

class PersonControllerTests extends GroovyTestCase {
// when true, Grails does a rollback after each test method
boolean transactional = false

void setUp() {
new Person(lastName:'Denoncourt', firstName:'Don', email:'ddenoncourt@cassevern.com').save()
}

void testShowWithReturnMap() {
def controller = new PersonController()
controller.params.id = 1
def model = controller.showWithReturnMap()
assertFalse "Person was found",
controller.flash.message ==~ /Person not found with id 1/
assertNotNull "Person returned as model", model
assertEquals "Person is a Denoncourt",
model.person.lastName, 'Denoncourt'
}

/* When a String is returned,
* it is available directly as the return value
* from the action
*/
void testShowWithReturnString() {
def controller = new PersonController()
controller.params.id = 1
def model = controller.showWithReturnString()
assertEquals "Don Denoncourt found",
model, "1 Don Denoncourt "
}

/* When the action does a render,
* use the controller's ModelAndView's model property
* to test model data
* Note that the view did not actually render but you can test
* the viewName property value
* What you can't seem to be able to do is see how the page
* would ultimately be rendered.
*/
void testShowWithRender() {
def controller = new PersonController()
controller.params.id = 1
controller.showWithRender()
assertTrue "Person was found",
!( controller.flash.message =~
/Person not found with id/
)
assertNotNull "Person returned in ModelAndView",
controller.modelAndView.model
assertEquals "Person is a Denoncourt",
controller.modelAndView.model.person.lastName,
'Denoncourt'
assertEquals "view should be show",
controller.modelAndView.viewName,
"/person/show"
}

/* When the action does a redirect, check the url with the controller's MockHttpServletResponse */
void testShowButRedirected() {
def controller = new PersonController()
controller.params.id = 9898
def model = controller.showWithReturnMap()
assertNotNull "Should have a flash message", controller.flash
assertTrue "Person should not be found",
controller.flash.message ==~
/Person not found with id 9898/
assertEquals "/person/list",
controller.response.redirectedUrl
}
/* When JSON is returned,
* use the controller.response.contentAsString
*/
void testShowWithJSON() {
def controller = new PersonController()
controller.params.id = 1
controller.showWithJSON()
assertEquals("""{"id":[1,"class":"Person",
"email":"ddenoncourt@cassevern.com",
"firstName":"Don","lastName":"Denoncourt]"}""",
controller.response.contentAsString)
}

/* to use a service,
* the test has to interject the service
*/
MathService mathService
void testUsesService() {
def controller = new PersonController()
controller.mathService = mathService
controller.params.a = 2
controller.params.b = 2
def model = controller.usesService()
assertEquals "2+2=4", model, "4"
}
}

3 comments:

Pam said...

Thanks for posting this - it is incredibly useful!

Seb said...

Thanks. Info still very valuable in 2010 !

Unknown said...

Hello,
The Article on Integration testing for Grails controllers is informative. It gives detailed information about it .Thanks for Sharing the information Integration testing for Grails controllers. Software Testing Company