Thursday, 20 October 2011

Grails unit tests and metaclass manipulation

There's some information out on Google regarding this issue I just ran into, but I figured a quick blog post can't hurt things :)

Problem:
- Manipulating a class' metaclass will affect that class' behaviour for every test method in that test class.
- We need to be able to remove our meta class changes after every test is executed to avoid test pollution: data setup for one test should not interfere with other tests.

How do we fix this?


import grails.test.*

class StaticClass {
public static String blah() {
return "blah."
}
}

class SomethingTests extends GrailsUnitTestCase {
def testOne() {
def result = StaticClass.blah()
assertEquals("blah.", result)
}

def testTwo() {
StaticClass.metaClass.'static'.blah = { "test" }
def result = StaticClass.blah()
assertEquals("test", result) // test pollution
}

def testThree() {
registerMetaClass(StaticClass)
StaticClass.metaClass.'static'.blah = { "something" }
def result = StaticClass.blah()
assertEquals("something", result)
}

def testFour() {
def result = StaticClass.blah()
assertEquals("test", result)
}
}


By extending GrailsUnitTestCase, we can call "registerMetaClass" (which unfortunately is not available in GroovyUnitTestCase) and pass the class name that we will be performing metaclass changes on. Grails will then automatically revert any metaclass changes that happen to this class after every test.


Important note: you must call this.setUp() if you define a setUp() method in your test case.

You can see in testTwo that the static metaclass change in testOne has persisted between tests. In testThree we do the regsiterMetaClass magic, which then resets the static method's return value to what we previously overwrote it with in testOne.


class AFewOtherTests extends GrailsUnitTestCase {
def testOne() {
registerMetaClass(StaticClass)
StaticClass.metaClass.'static'.blah = { "test" }
def result = StaticClass.blah()
assertEquals("test", result)
}

def testTwo() {
def result = StaticClass.blah()
assertEquals("blah.", result)
}
}

No comments: