Wednesday, August 13, 2008

Grails Tests: Cannot send redirect - response is already committed

When testing controllers, if you make more than one call to a action/closure you may get the error: "Cannot send redirect - response is already committed"
But if you want to call one action to modify the database and then test the results in a subsequent call, if you put that call in a different test method, the prior test's transaction will probably be rolled back. So you want to make both action/closure calls in the same test method.

To get around this mess I simply MOPed it up (with help from Venkat Subramaniam's book "Programming Groovy"):


controller.metaClass.redirect = { Map args -> return args}


Note that the action of the redirect will not be invoked.
Also note that I am MOPing an instance, not the Class.

Grails Controller Tests: ModelAndView

I just joined the team at AlterThought.com and my first task was to revitalize the integration test for a bunch of controllers. But Grails no longer seems to have the modelAndView as a standard attribute of a controller. And we have assertions like:


assert controller.modelAndView.model.contractList.size() > 1


My solution was to override the controller's render method and stuff in a modelAndView to the controller myself:


import org.springframework.web.servlet.ModelAndView

class IntegrationTestUtil {
void overrideRender(controller) {
def dir = controller.class.name.replaceAll(/Controller/, '')
dir = dir[0].toLowerCase() + dir.substring(1)
def originalRender =
controller.metaClass.getMetaMethod("render", [Map] as Class[])
controller.metaClass.render = { Map args ->
if(args.view && args.model) {
delegate.metaClass.getModelAndView = {->
new ModelAndView("/${dir}/${args.view}", args.model)
}
} else {
originalRender.invoke(delegate, args)
}
}
}
}


My controllers then invoke the overrideRender method in setUp and the test run fine.

Note the override only changes render invocations that pass a map with model and view entries.