eustachio
eustachio

Reputation: 168

Unit testing Controllers with 'withFormat' and html content type gives empty response unless 'render' explicitly called

In a Grails 2.1.1 application, I'm trying to unit test a controller that uses 'withFormat' to render the response either as HTML or JSON. However responding to the HTML content type always results in an empty response in my test, unless I wrap it in a closure and explicitly call 'render'. For JSON, it sends back the expected response.

Controller:

import grails.converters.JSON

class TestController {
def formatWithHtmlOrJson() {
    withFormat {
        html someContent:"should be HTML" 
        json {render new Expando(someContent:"should be JSON")  as JSON}
    }
}

Test:

@TestFor(TestController)
class TestControllerTests {
    void testForJson() {
        response.format = "json"
        controller.formatWithHtmlOrJson()
        println("resp: $response.contentAsString")
        assert response.json.properties.someContent == "should be JSON"
    }

    void testForHtml() {
        response.format = "html"
        controller.formatWithHtmlOrJson()
        println("resp: $response.contentAsString")
        // fails here, response is empty
        assert response.text
        //never gets this far
        assert response.contentAsString.contains("HTML")
    }
}

As described above, for JSON this works, but for HTML I always get an empty response, unless I wrap the html check in a closure and explicitly call render, as below:

withFormat {
    html {
        render someContent:"should be HTML" 
    }

The docs suggest I shouldn't need to do this, e.g. :

withFormat {
    html bookList: books
    js { render "alert('hello')" }
    xml { render books as XML }
}

from http://grails.org/doc/2.2.x/ref/Controllers/withFormat.html

Frustratingly, the grails docs on testing mention the use of withFormat but only give examples for testing xml/json, and nothing for the html response.

http://grails.org/doc/latest/guide/testing.html#unitTestingControllers

Can anyone explain this discrepancy, or how I might work around it in my tests?

Upvotes: 2

Views: 2156

Answers (3)

eustachio
eustachio

Reputation: 168

Finally figured this out.

In the documentation (http://grails.org/doc/latest/guide/testing.html#unitTestingControllers), it mentions this way of testing a controller response under Testing Actions Which Return A Map :

import grails.test.mixin.*
@TestFor(SimpleController)
class SimpleControllerTests {

    void testShowBookDetails() {
        def model = controller.showBookDetails()
        assert model.author == 'Alvin Plantinga' 
    }
}

The same approach works for controller methods that use withFormat.

So for my original example above:

withFormat {
    html someContent:"should be HTML" 
...

the test becomes:

void testForHtml() {
    response.format = "html"
    def model = controller.formatWithHtmlOrJson()
    assert model.someContent == "should be HTML"
}

The documentation is a tad confusing as the withFormat section makes no mention of this approach.

Worth noting, should anyone else encounter this, that if the html block is inside a closure, the map isn't returned, but rather the value of the map entry, so for the controller code:

withFormat{
    html{
        someContent:"should be HTML" 
    }...

the test check becomes:

     assert model == "should be HTML"

Alternatively, if you can modify the controller code, returning the result inside a map lets you use the dot notation to check the element value. For this code:

 withFormat {
    html {
        [someContent:"should be HTML"] 
    }....

the test check is:

assert model.someContent == "should be HTML"

Also worth noting, in my original example that doesn't use a closure for the HTML type, you can't return the value as a map- it results in a compile error.

//Don't do this, won't compile
    withFormat {
        html [someContent:"should be HTML"]         
    ...

Upvotes: 3

dmahapatro
dmahapatro

Reputation: 50265

Suggestion provided in the document does not use a closure and has this verbiage "Grails searches for a view called grails-app/views/book/list.html.gsp and if that is not found fallback to grails-app/views/book/list.gsp".

When you use a closure for html then we have to either return a model to the action or render the content. Since in this case you are using the html closure the content has to be rendered.

Using a Closure (Your use case passed)

withFormat{
   html {
      test: "HTML Content" //This will not render any content
      render(test: "HTML Content") //This has to be used
   }
}

Upvotes: 0

Fabiano Taioli
Fabiano Taioli

Reputation: 5540

try

withFormat {
    html {
        println("html")
        [new Expando(test:"html")]
    }
}

Upvotes: 0

Related Questions