使用Groovy MetaClass覆盖方法

rao*_*son 14 groovy unit-testing programming-languages dynamic-languages mocking

我有一个POJO使用服务来做某事:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一个Groovy测试用例:

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个测试方法testInjectedMockIFace按预期工作:POJO是使用动态实现创建的IService.当callX被调用时,它只是返回"很时髦".这样,服务就被嘲笑了.

但是我不明白为什么第二种方法testMetaClass不能按预期工作,而是在尝试调用callX服务对象时抛出NullPointerException .我以为我doCallService用这一行覆盖了这个方法:

pojo.metaClass.doCallService = { String s ->
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

谢谢!

noa*_*oah 18

如果您的POJO确实是Java类,而不是Groovy类,那么这就是您的问题.Java类不通过metaClass调用方法.例如,在Groovy中:

pojo.publicMethod('arg')
Run Code Online (Sandbox Code Playgroud)

相当于这个Java:

pojo.getMetaClass().invokeMethod('publicMethod','arg');
Run Code Online (Sandbox Code Playgroud)

invokeMethod通过metaClass发送调用.但这种方法:

public String publicMethod(String x) {
    return doCallService(x);
}
Run Code Online (Sandbox Code Playgroud)

是一种Java方法.它不invokeMethod用于打电话doCallService.要使代码工作,PlainOldJavaObject需要是一个Groovy类,以便所有调用都将通过metaClass.普通Java代码不使用metaClasses.

简而言之:即使Groovy也不能覆盖Java方法调用,它只能覆盖来自Groovy的调用或者通过invokeMethod调度.

  • 如果它在.groovy文件中,则它是一个Groovy类. (4认同)

l15*_*15a 18

你的语法有点偏.问题是pojo是一个Java对象,没有metaClass.使用ExpandoMetaClass拦截对PlainOldJavaObject的doCallService的调用:

只需更换:

    pojo.metaClass.doCallService = { String s ->
        "no service"
    }
Run Code Online (Sandbox Code Playgroud)

附:

    PlainOldJavaObject.metaClass.doCallService = { String s ->
        "no service"
    }
Run Code Online (Sandbox Code Playgroud)

  • 这里要记住的一件事是当你操纵Class的metaClass时,从那一点开始的每个实例都将被操纵.这会对在同一会话中运行的其他测试产生重大影响.操作类的实例时,只会影响该实例. (5认同)