My Octopress Blog

A blogging framework for hackers.

Grails Testing Issue When Rendering as 'Text/json'

I ran into this issue while unit testing my controllers (many of which only return JSON) in Grails. I’ve reported the issue to grails: http://jira.codehaus.org/browse/GRAILS-7339 but it’s also worth noting here, so anyone else left scratching their head as to why the rendering doesn’t appear to be working in their unit tests can find the workaround as well:

If you are writing a unit or integration test in grails and mocking the controller, which can be done either by extending ControllerUnitTestCase (or using mockController) you might run into this issue. As you probably know, there are multiple ways to render JSON in grails. One is to take a normal map and render as JSON:


def jsonMap = [success:true]
render jsonMap as JSON


This will work fine everywhere, because you’re effectively passing it in as a converter. However, if you use the more verbose (and in many ways more powerful) rendering, you will run into issues:


render(contentType: "text/json") {
success(true)
results {
result.each {
def period = it.getPeriod()
b(id: it.id,name: it.name)
}
}
}


In my case, my test looks like this:


assert JSON.parse(this.controller.response.contentAsString)?.success == true


This won’t work, because the content on the mock response will be null. With a little digging into how mockController() works, you will find the problem lies in MockUtils. Specifically, in the method that handles a map with render options and a closure:


clazz.metaClass.render = {Map map, Closure c ->
renderArgs.putAll(map)

switch(map["contentType"]) {
case null:
break

case "application/xml":
case "text/xml":
def b = new StreamingMarkupBuilder()
if (map["encoding"]) b.encoding = map["encoding"]

def writable = b.bind(c)
delegate.response.outputStream << writable
break

default:
println "Nothing"
break
}
}


As you can see, the case of ‘text/json’ isn’t handled. I was able to workaround this issue by creating a custom unit test case class to extend from the Grails ControllerUnitTestCase:


class CustomControllerTestCase extends ControllerUnitTestCase{

protected void setUp() {
super.setUp()
controller.class.metaClass.render = {Map map, Closure c ->
renderArgs.putAll(map)

switch(map["contentType"]) {
case null:
break

case "application/xml":
case "text/xml":
def b = new StreamingMarkupBuilder()
if (map["encoding"]) b.encoding = map["encoding"]

def writable = b.bind(c)
delegate.response.outputStream << writable
break

case "text/json":
new JSonBuilder(delegate.response).json(c)
break
default:
println "Nothing"
break
}
}
}

}


Since it’s groovy, you could pretty much replace this anywhere by overriding your mock controller’s meta class.

Comments

olikaf
you saved my day :-)
Robert
Thanks Lucas, this problem looked like it was going to start eating up the rest of my afternoon. Your solution did the trick.