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 teach software development training courses. I specialize in all areas of Java and XML, from EJB3 to web services to open source projects like Spring, Hibernate, Groovy, and Grails. Find me on Google+ I am the author of "Making Java Groovy", a Java / Groovy integration book published by Manning in the Fall of 2013, and "Gradle Recipes for Android", published by O'Reilly in 2015.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: