6

Using Grails 2.1.0

It seems that doing this from a controller:

render(view: "someView", model: [modelEntry: "hello"]) 

allows me to do this in a unit test for that controller:

controller.method() 
assert model.modelEntry == "hello" 

However, if I change the controller to do this:

render(template: "someTemplate", model: [modelEntry: "hello"]) 

Now the model instance in the test is an empty array. I've done quite a bit of searching about this, and most of the solutions seem to be for Grails 1, often involving the modelAndView object (which doesn't exist in my test) or renderArgs (ditto).

The only solution I've found is to manually override the views within the test, like this:

views['_someTemplate.gsp'] = '${modelEntry}'

and then making assertions about the string. But I dislike this solution because it:

  1. requires the test knows the filename of the template
  2. makes it difficult to test model entries that don't have good toString() methods
  3. makes it difficult to make multiple assertions about related model entries.

Is there any way to more directly get at the entries in the model from a test case when the controller renders a template?

Rod
  • 137
  • 6
  • `controller.modelAndView.model` don't exists? –  Feb 28 '13 at 17:09
  • @SérgioMichels correct, `controller.modelAndView` is null. Keep in mind, this is using the idiomatic Grails 2 style, where a test is declared as being a `@TestFor(WhateverController)` and the test does not extend anything. I'm not sure if the modelAndView thing works for grails 1 or why I always see that suggestion, but it's not in this type of test for Grails 2 anyway. – Rod Feb 28 '13 at 19:26

1 Answers1

10

Digging a little bit in the code of the render method (org.codehaus.groovy.grails.web.metaclass.RenderDynamicMethod) I can see that the modelAndView is setted only when you render a view.

Rendering a template will return a null modelAndView indeed.

To inspect the model in this case I think you can use the Groovy metaClass. The idea is to intercept the original method, store the value and then call him.

Based on this question, I builded this (not tested, may need adjusts):

@TestFor(MyController)
class MyControllerTests

  def templateModel

  @Test
  void inspectTemplateModel() {
    def originalMethod = MyController.metaClass.getMetaMethod('render', [Map] as Class[])
    controller.metaClass.render = { Map args ->
      templateModel = args.model
      originalMethod.invoke(delegate, args)
    }

    controller.method()
    assert templateModel.modelEntry == 'foo'

}
Community
  • 1
  • 1
  • Yep, that worked. Kind of amazing that it requires this kind of trickery. I've actually taken your solution a step further, instead of templateModel I actually assign a modelAndView on the controller instance, making the rest of the code work like you'd expect if it'd been a view – Rod Feb 28 '13 at 20:30
  • Well, you can raise a [JIRA](http://jira.grails.org/browse/GRAILS), asking for a better way of access to the template model to your tests. This is the beauty of the improvement :) –  Feb 28 '13 at 20:37