This is a small issue, but I encountered it and found a solution on the mailing lists, so I thought I’d document it here.

I was demonstrating a trivial Grails application in class today and decided to unit test it. The app has a single controller, called WelcomeController:

[sourcecode language=”groovy”]
class WelcomeController {
def index = {
def name = params.name ?: "Grails"
render "Hello, $name"
}
}
[/sourcecode]

When I deploy the application and access the Welcome controller (via http://localhost:8080/hellograils/welcome/index), it displays “Hello, Grails!”. If I append “?name=Dolly” to the URL, the result is “Hello, Dolly!”. All nice and simple.

I decided I wanted to write a test case for this, and lately I’ve been learning how to favor unit tests over integration tests as much as possible, mostly for speed. I therefore wrote the following tests:

[sourcecode language=”groovy”]
import grails.test.*

class WelcomeControllerTests extends ControllerUnitTestCase {
void testWelcomeWithoutParameter() {
def wc = new WelcomeController()
wc.index()
assertEquals "Hello, Grails!", wc.response.contentAsString
}

void testWelcomeWithParameter() {
def wc = new WelcomeController()
wc.params.name = "Dolly"
wc.index()
assertEquals "Hello, Dolly!", wc.response.contentAsString
}
}
[/sourcecode]

When I run the unit tests (i.e., grails test-app unit:), everything runs correctly.

One of the students pointed out that though this is a trivial example, it’s open to XSS (cross-site scripting) attacks. In the URL, replace “name=Dolly” with “name=alert('dude, you've been hacked')” and the embedded JavaScript code executes and pops up an alert box.

I knew that an easy solution to this would be to modify the index action in the controller to look like:
[sourcecode language=”groovy”]
class WelcomeController {
def index = {
def name = params.name ?: "Grails"
render "Hello, $name".encodeAsHTML()
}
}
[/sourcecode]

The “encodeAsHTML” method escapes all the HTML, so the output of the hack is just “Hello, alert(…” (i.e., the script is shown as a string, rather than executed) and the problem goes away.

The issue I encountered, though, is that my unit tests started failing, with a missing method exception that claimed that the String class doesn’t have a method called encodeAsHTML. That’s correct, of course, because that method is dynamically injected by Grails based on the org.codehaus.groovy.grails.plugin.codecs.HTMLCodec class. In a unit test, though, the injection doesn’t happen, and I get the exception.

One solution to this, as pointed out on the very valuable grails-users email list, is to add the method to the String class via its metaclass. In other words, in my test, I can add

[sourcecode language=”groovy”]
void setUp() {
super.setUp()
String.metaclass.encodeAsHTML = {
org.codehaus.groovy.grails.plugins.codecs.HTMLCodec.encode(delegate)
}
}
[/sourcecode]

Now the String class has the encodeAsHTML method, and everything works again.

Then I started browsing the Grails API, and found that in ControllerUnitTestCase there’s a method called loadCodec. The GroovyDocs weren’t very informative, but I found in the jira for Grails that issue GRAILS-3816 recommends the addition of the loadCodec method for just this sort of purpose.

That means that I can actually write
[sourcecode language=”groovy”]
void setUp() {
super.setUp()
loadCode(org.codehaus.groovy.grails.plugins.codecs.HTMLCodec)
}
[/sourcecode]
and everything works as it should. Since this isn’t terribly well documented, I thought I’d say something here. Hopefully this will save somebody some looking around.

14 thoughts on “Using a codec in a Grails unit test

  1. A couple of typos: I wrapped the XSS attack in a <script> tag, but of course when I posted the result here the tag was lost. I should have escaped the angle brackets.

    Also, in the last code listing, the method is loadCodec, not loadCode.

  2. Thanks! Had the very same problem a couple of days ago but solved it with a metaClass-hack, this is a much nicer solution.

  3. Another little type – the code should say String.metaClass, not String.metaclass

  4. Came across a similar use case in which I had to use a bit of code which was using encodeAsURL()

    This approach worked like a charm! Thanks. 🙂

    — Vivek

  5. I found you can use the loadCodec to access the included codecs… For some reason, the customers ones in grails-app/utils don’t load either directly or via the loadCodec.

    What I used.

    import org.codehaus.groovy.grails.plugins.codecs.*

    .
    .
    .
    protected void setUp() {
    super.setUp()

    println “loading codecs”
    loadCodec (HTMLCodec)
    loadCodec (Base64Codec)
    loadCodec (SHA1Codec)
    // loadCodec (UnderscoreCodec) // custom but if included, not loaded for some reason.

    println “loaded Codecs”
    loadedCodecs.each { println it.toString() }
    }
    .
    .
    .
    void testSimpleConstraints() {
    // in order to not need to be an intgr. test.
    //
    mockLogging(User)
    mockForConstraintsTests(User)
    def user = new User(login:”someone”,
    password:”blah”.encodeAsSHA1(),
    role:”SuperUser”)
    // oops—role should be either ‘admin’ or ‘user’
    // will the validation pick that up?
    assertFalse user.validate()
    // mocking check.
    assertEquals “inList”, user.errors[“role”]
    }

  6. this helped a lot! although my final solution is using String.metaClass, not String.metaclass like the original post says (thanks to the commenter that noticed this also)

  7. Thanks, this helped me see why my code was failing!

    I think for Unit tests, you should mock the encodeAsHTML method, since your not testing its code.

    For example:
    String.metaClass.encodeAsHTML = { -> return ‘Some String’ }

    This way you can just test that it was called.

    Thanks again!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.