I stumbled across this when doing a refactoring with Grails 2.0.1 but I pulled the basics of this problem out into a straight groovy 1.8.6 test and it still failed. I came across it because my method used to take no parameters and I changed it to take 1 parameter. When I changed the implementing production code none of my tests failed. Which is weird because the metaClassing that I had in the test was setup to accept no parameters but it was still responding to my production code when I passed in a parameter. So in the example below I’m wondering why the second metaClassing is being invoked and not the first. It doesn’t accept any parameters and as you can see I’m passing one in. If you switch the order of the metaClassing then it works appropriately but order shouldn’t matter in this case since the method signature is different. Any insight into why this is happening would be greatly appreciated.
import groovy.util.GroovyTestCase
class FirstTest extends GroovyTestCase {
void testStuff() {
def object = new Object()
object.metaClass.someMethodName = {Object obj ->
"ONE"
}
object.metaClass.someMethodName = {
"TWO"
}
def result = object.someMethodName(new Object())
assert "ONE" == result //result is equal to "TWO" in this case
}
}
EDIT
Seems as if my above code may be more confusing than helpful so here is the actual code.
Original Production Code:
def create() {
render(view: "create", model: [domains: Domain.myCustomListMethod().sort{it.cn}])
}
Original Test Code:
@Test
void createShouldIncludeAListOfAllDomainsInModel() {
def directory = GldapoDirectory.newInstance(
"", [
url: "http://url.com",
userDn: "someUserName",
password: "superSecretPassword"
])
controller.session.userDirectory = directory
Domain.metaClass.'static'.myCustomListMethod = {
[[cn:"1"], [cn:"2"]]
}
controller.create()
assert [[cn:"1"], [cn:"2"]] == controller.modelAndView.model.domains
}
I then updated the production code to pass in the session.userDirectory and my test still passed unmodified even though it’s not setup to receive any parameters:
def create() {
render(view: "create", model: [domains: Domain.list(session.userDirectory).sort{it.cn}])
}
Closures by default take one parameter (of class Object), even if none is declared (accessible via the default variable
it)So your second closure is overriding the first