使用Groovy MetaClass覆盖方法

时间:2009-12-18 11:55:17

标签: unit-testing programming-languages groovy mocking dynamic-languages

我有一个使用服务做某事的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);
    }
}

我有一个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")
    }
}

第一个测试方法testInjectedMockIFace按预期工作:使用动态实现IService创建POJO。调用callX时,它只返回“非常常规”。这样,服务就被嘲笑了。

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

pojo.metaClass.doCallService = { String s ->

我做错了什么?

谢谢!

3 个答案:

答案 0 :(得分:18)

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

pojo.publicMethod('arg')

等同于这个Java:

pojo.getMetaClass().invokeMethod('publicMethod','arg');

invokeMethod通过metaClass发送调用。但是这个方法:

public String publicMethod(String x) {
    return doCallService(x);
}

是一种Java方法。它不会使用invokeMethod来调用doCallService。为了使代码工作,PlainOldJavaObject需要是一个Groovy类,以便所有调用都将通过metaClass。普通的Java代码不使用metaClasses。

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

答案 1 :(得分:18)

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

只需替换:

    pojo.metaClass.doCallService = { String s ->
        "no service"
    }

使用:

    PlainOldJavaObject.metaClass.doCallService = { String s ->
        "no service"
    }

答案 2 :(得分:1)

你看起来很好。我在groovy控制台webapp上运行了一个稍微修改过的版本,它运行没有问题。请在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")

您使用的是什么版本的Groovy。它很可能是元类实现中Groovy中的一个错误。 groovy语言移动得非常快,元类实现从版本变为版本。

编辑 - 评论的反馈:

groovy控制台webapp的版本是1.7-rc-1。因此看起来该版本可能会按照您的要求运行。他们目前在RC2,所以我希望它很快就会发布。不确定你看到的是一个错误,还是它在1.6.x版本中的工作方式不同。