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 of IService
. When callX
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 invoke callX
on the service object. I thought I had overwritten the doCallService
method with this line:
pojo.metaClass.doCallService = { String s ->
What am I doing wrong?
Thanks!
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"
}
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.
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.
来源:https://stackoverflow.com/questions/1927796/using-groovy-metaclass-to-overwrite-methods