Using a codec in a Grails unit test

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:

class WelcomeController {
  def index = {
    def name = params.name ?: "Grails"
    render "Hello, $name"
  }
}

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:

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
  }
}

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:

class WelcomeController {
  def index = {
    def name = params.name ?: "Grails"
    render "Hello, $name".encodeAsHTML()
  }
}

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

  void setUp() {
    super.setUp()
    String.metaclass.encodeAsHTML = {
      org.codehaus.groovy.grails.plugins.codecs.HTMLCodec.encode(delegate)
    }
  }

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

  void setUp() {
    super.setUp()
    loadCode(org.codehaus.groovy.grails.plugins.codecs.HTMLCodec)
  }

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.

About Ken Kousen
I am a Java Champion and the author of the books "Modern Java Recipes" (O'Reilly Media), "Gradle Recipes for Android" (O'Reilly Media), and "Making Java Groovy" (Manning), as well as over a dozen video courses at Safari Books Online. I'm a regular member of the No Fluff, Just Stuff conference tour and have given talks all over the world. Through my company, Kousen IT, Inc, I've taught training courses to and worked with thousands of developers.

13 Responses to Using a codec in a Grails unit test

  1. Ken Kousen says:

    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. Leo says:

    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. Simon Green says:

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

  4. j pimmel says:

    Sweet, thanks.. asked the question; found the answer

    Nice

  5. Vivek says:

    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

  6. Jim B. says:

    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”]
    }

  7. dave says:

    Thanks, this was really helpful

  8. Pingback: Podcast grails.org.mx: Episodio 6 de la Temporada 1: Desarrollo web con Groovy | GrailsMX

  9. Thanks for this!
    I’m happy to discover all these.

  10. mathif22 says:

    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)

  11. Pingback: Podcast grails.org.mx: Episodio 6 de la Temporada 1: Desarrollo web con Groovy |

  12. Ramson Tutte says:

    Grails 2.4.3 GrailsUnitTestMixin offers mockCodec method.
    http://grails.org/doc/latest/api/grails/test/mixin/support/GrailsUnitTestMixin.html#mockCodec%28Class%3C?%3E%29

    @TestMixin(GrailsUnitTestMixin)
    class CheckForOrderUpdateJobSpec extends Specification {

    def setup() {
    mockCodec(MD5Codec.class)
    }

    }

  13. Pingback: 在Grails单元测试中使用Codec的两种方法 – grails开发平台

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: