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:
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:
In my case, my test looks like this:
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:
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:
Since it’s groovy, you could pretty much replace this anywhere by overriding your mock controller’s meta class.
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
you saved my day :-)
Thanks Lucas, this problem looked like it was going to start eating up the rest of my afternoon. Your solution did the trick.