Using Groovy MetaClass to overwrite Methods
Solution 1
Your syntax is a tiny bit off. The problem is that pojo is a Java Object and does not have a metaClass. To intercept calls to PlainOldJavaObject's doCallService using ExpandoMetaClass:
Just replace:
pojo.metaClass.doCallService = { String s ->
"no service"
}
With:
PlainOldJavaObject.metaClass.doCallService = { String s ->
"no service"
}
Solution 2
If your POJO really is a Java class, and not a Groovy class, then that is your problem. Java classes don't invoke methods via the metaClass. e.g., in Groovy:
pojo.publicMethod('arg')
is equivalent to this Java:
pojo.getMetaClass().invokeMethod('publicMethod','arg');
invokeMethod
sends the call through the metaClass. But this method:
public String publicMethod(String x) {
return doCallService(x);
}
is a Java method. It doesn't use invokeMethod
to call doCallService
. To get your code to work, PlainOldJavaObject
needs to be a Groovy class so that all calls will go through the metaClass. Normal Java code doesn't use metaClasses.
In short: even Groovy can't override Java method calls, it can only override calls from Groovy or that otherwise dispatch through invokeMethod.
Solution 3
What you have looks fine. I ran a slightly modified version on it on the groovy console webapp and it ran without issue. See for yourself using this code at http://groovyconsole.appspot.com/.
public interface IService {
String callX(Object o);
}
public class PlainOldJavaObject {
private IService service;
public String publicMethod(String x) {
return doCallService(x);
}
public String doCallService(String x) {
if(service == null) {
throw new RuntimeException("Service must not be null");
}
return service.callX(x);
}
}
def pojo = new PlainOldJavaObject()
pojo.metaClass.doCallService = { String s ->
"no service"
}
println pojo.publicMethod("arg")
What version of Groovy are you using. It could very well be a bug in Groovy in the metaclass implementation. The groovy language moves pretty quickly and the metaclass implementation changes from version to version.
Edit - Feedback from Comment:
The version of the groovy console webapp is 1.7-rc-1. So it looks like that version may work as you want it to. They are currently in RC2 so I expect it would be released soon. Not sure if what you are seeing is a bug or just a difference in how it works in the 1.6.x version.
![raoulsson](https://i.stack.imgur.com/D8nV6.jpg?s=256&g=1)
raoulsson
Before: CTO Contovista AG, Zurich, Co-founder Zorp Technologies Inc., SF, Manager and Chief System Architect at Leonteq, and many more... Experienced software engineer and teamlead looking to build/enable useful, delightful, and meaningful products. Passionate, hard-worker interested in contributing to team-oriented, strong engineering cultures. Proven track record of hiring and running successful teams.
Updated on April 21, 2021Comments
-
raoulsson about 3 years
I have a POJO that uses a service to do something:
public class PlainOldJavaObject { private IService service; public String publicMethod(String x) { return doCallService(x); } public String doCallService(String x) { if(service == null) { throw new RuntimeException("Service must not be null"); } return service.callX(x); } public interface IService { String callX(Object o); } }
And I have a Groovy test case:
class GTest extends GroovyTestCase { def testInjectedMockIFace() { def pojo = new PlainOldJavaObject( service: { callX: "very groovy" } as IService ) assert "very groovy" == pojo.publicMethod("arg") } def testMetaClass() { def pojo = new PlainOldJavaObject() pojo.metaClass.doCallService = { String s -> "no service" } assert "no service" == pojo.publicMethod("arg") } }
The first test method,
testInjectedMockIFace
works as expected: The POJO is created with a dynamic implementation ofIService
. WhencallX
is invoked, it simply returns "very groovy". This way, the service is mocked out.However I don't understand why the second method,
testMetaClass
does not work as expected but instead throws a NullPointerException when trying to invokecallX
on the service object. I thought I had overwritten thedoCallService
method with this line:pojo.metaClass.doCallService = { String s ->
What am I doing wrong?
Thanks!
-
raoulsson over 14 yearsHi Chris, I run Groovy Version: 1.6.5 JVM: 1.6.0_13
-
noah over 14 yearsThe version has nothing to do with it. The problem is that doCallService(x) is Java code, not Groovy code, so it isn't metaClass aware.
-
Tomato over 11 yearsHow do you properly distinguish between Groovy code and Java code? How would you make PlainOldGroovyObject instead of PlainOldJavaObject?
-
noah over 11 yearsIf it's in a .groovy file, it's a Groovy class.
-
A.J. Brown about 11 yearsOne thing to keep in mind here is when you manipulate the Class's metaClass, every instance from that point forward will be manipulated. This can have a big impact on other tests that run in the same session. When you manipulate an instance of a class, only that instance is affected.
-
Mate Šimović over 5 yearsTo achieve complete test isolation, so that your manipulation on the metaClass won't impact other tests you could do the following. Remember the old method (eg., def oldMethod = pojo.&doCallService) override it (pojo.metaClass.doCallService = { String s -> "no service" }) at the beginning of your test and return the old method at the end of the test (pojo.metaClass.doCallService = oldMethod). NOTE: this will only work if the method doCallService is NOT overloaded (multiple doCallService methods with different arguments) since 'def oldMethod = pojo.&doCallService' can't know which to take.