【问题标题】:Using Groovy MetaClass to overwrite Methods使用 Groovy MetaClass 覆盖方法
【发布时间】:2010-12-28 00:29:27
【问题描述】:

我有一个使用服务做某事的 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 按预期工作:POJO 是使用IService 的动态实现创建的。当callX 被调用时,它只返回“非常时髦”。这样,服务就被模拟出来了。

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

pojo.metaClass.doCallService = { String s ->

我做错了什么?

谢谢!

【问题讨论】:

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


    【解决方案1】:

    你的语法有点不对劲。问题是 pojo 是一个 Java 对象并且没有元类。使用 ExpandoMetaClass 拦截对 PlainOldJavaObject 的 doCallService 的调用:

    只需替换:

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

    与:

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

    【讨论】:

    • 这里要记住的一点是,当您操作类的元类时,从该点开始的每个实例都将被操作。这会对在同一会话中运行的其他测试产生很大影响。当你操作一个类的实例时,只有那个实例会受到影响。
    • 要实现完全的测试隔离,以便您对元类的操作不会影响其他测试,您可以执行以下操作。记住旧方法(例如,def oldMethod = pojo.&doCallService)在测试开始时覆盖它(pojo.metaClass.doCallService = { String s -> "no service" })并在测试结束时返回旧方法测试(pojo.metaClass.doCallService = oldMethod)。注意:这仅在方法 doCallService 未重载(具有不同参数的多个 doCallService 方法)时才有效,因为 'def oldMethod = pojo.&doCallService' 不知道该采用哪个。
    【解决方案2】:

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

    pojo.publicMethod('arg')
    

    相当于这个Java:

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

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

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

    是一种 Java 方法。它不使用invokeMethod 调用doCallService。要使您的代码正常工作,PlainOldJavaObject 需要是一个 Groovy 类,以便所有调用都通过 metaClass。普通的 Java 代码不使用元类。

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

    【讨论】:

    • 如何正确区分 Groovy 代码和 Java 代码?你将如何制作 PlainOldGroovyObject 而不是 PlainOldJavaObject?
    • 如果它在一个 .groovy 文件中,它就是一个 Groovy 类。
    【解决方案3】:

    你所拥有的看起来不错。我在 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 版本中的工作方式不同。

    【讨论】:

    • 嗨,克里斯,我运行 Groovy 版本:1.6.5 JVM:1.6.0_13
    • 与版本无关。问题是 doCallService(x) 是 Java 代码,而不是 Groovy 代码,所以它不支持元类。
    猜你喜欢
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 2016-06-12
    • 2015-04-30
    • 2018-09-14
    • 2015-08-28
    • 2011-07-02
    • 1970-01-01
    相关资源
    最近更新 更多