1

I am trying to get a custom JSON renderer for exceptions working in my REST api.

I was able to get a custom marshaller working that did most of what I needed, but I would like to have control over the context that I don't have access to in the marshaller. The grails documentation shows how to write a custom renderer, and I have one that I think should work, but I can't use it during unit testing my REST controller.

Grails docs: http://grails.org/doc/2.3.4/guide/webServices.html#customRenderers

Does anyone know how I would initialize this renderer to be used in my controller actions during unit testing?

The above documentation only says how to set it up in the resources.groovy file.

When I was using the marshaller, I was able to do:

def setup(){
    //Set up the custom JSON marshallers
    JSON.registerObjectMarshaller(new CusomMarshaller(), 1)
}

But I don't see an equivalent method for Renderers. Can anyone point me in the right direction?


Further details:

Here is my renderer:

class JSONExceptionRenderer extends AbstractRenderer<Exception>{

JSONExceptionRenderer(){
    super(Exception, [MimeType.JSON, MimeType.HAL_JSON, MimeType.TEXT_JSON] as MimeType[])
}

@Override
void render(Exception object, RenderContext context) {
    log.warn("RENDERING")
    Exception exception = (Exception) object

    //Default to internal error
    Integer code = 500

    //If it is a defined exception with a more appropriate error code, then set it
    if(exception instanceof RestException){
        code = (Integer) ((RestException) exception).getCode()
    }else if(exception instanceof MissingResourceException){
        code = 404
    }
    context.status = HttpStatus.valueOf(code)

    //Write the JSON
    Writer writer = context.getWriter()
    Map content = ["code":code, "status":"error", "message":exception.message]
    JsonBuilder builder = new JsonBuilder(content)
    builder.writeTo(writer)

}
}

And this is the way I am trying to get it to work:

try{
  log.info "Throwing exception"
  throw new NullPointerException("Test Exception")
}catch(Exception ex){
  render ex as JSON
}

Thanks!

Tim Overly
  • 425
  • 4
  • 10

2 Answers2

1

If you are using spock, you can inject the bean directly in Specification.

@TestFor(MyController)
class MyControllerSpec extends spock.lang.Specification {
     def myCustomRenderer //bean name used in resources.groovy

     //continue with tests
}

If you are using junit tests, then you can use defineBeans as:

@TestFor(MyController)
class MyControllerTests {
    void setup() {
         defineBeans {
            myCustomRenderer(com.example.MyCustomRenderer)
         }
    }

    //continue with tests
}

You can refer this answer as well for use of defineBeans.

I believe this is what you just need to do to test the behavior of the renderer.

Community
  • 1
  • 1
dmahapatro
  • 49,365
  • 7
  • 88
  • 117
  • That was what I was looking for, but it still doesn't seem to use the renderer that I made and setup. I have attached to the original post my specific renderer and how I am calling it. – Tim Overly Feb 25 '14 at 16:16
  • Maybe that means this question was "answered" ;-) – Tim Overly Feb 25 '14 at 17:13
  • Have you registered the renderer as a bean in `resources.groovy`? – dmahapatro Feb 25 '14 at 17:30
  • Yea, and it doesn't get called from there either. Do you know what "triggers" a particular renderer to get called? – Tim Overly Feb 25 '14 at 18:39
  • I don't see a `contentType` being set to the `RenderContext` inside `render()` in the custom renderer. Shouldn't there be `context.contentType = MimeType.JSON.name`. I may be incorrect, I have not fully tested the renderer myself. – dmahapatro Feb 25 '14 at 18:53
  • That is probably true, and thank you for pointing it out, but I am not even "entering" the render method. I thought the renderer would be triggered by the MIME type used in the constructor, so in my case any JSON type. or if I did ```render CLASS as JSON``` it would find any renderer that was set up with CLASS with TYPE of JSON. – Tim Overly Feb 25 '14 at 19:13
  • Have you also tried `respond ex` instead of `render ex as JSON`. I am unable to test this feature right now. Will give it a shot when feasible. – dmahapatro Feb 25 '14 at 19:30
  • Ha! Just saw this, and I came to the same conclusion. – Tim Overly Feb 25 '14 at 20:10
0

After much digging around in the source. I thought I would post this here for others.

The reason my custom renderer didn't work was you have to use the "respond" method on the controller:

respond object

That will check the class ControllersRestApi with a very large respond method to find your renderer in the rendererRegistry and use it. This is different than the object marshallers which use the render as notation.

In addition you need to also flush the writer, which wasn't in the orgininal documentaiton:

builder.writeTo(writer)
writer.flush()
Tim Overly
  • 425
  • 4
  • 10