微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

groovy – 使用metaClass的Mock Gradle project.exec {…}

作为测试Gradle插件的一部分,我想要一个groovy方法:project.exec {…}.这是为了确认它正在进行正确的命令行调用.我正在尝试使用元编程:

Project proj = ProjectBuilder.builder().build()

proj.MetaClass.exec = { Closure obj ->
    println 'MOCK EXEC'
}

proj.exec {
    executable 'echo'
    args 'PROJECT EXEC'
}
// prints 'PROJECT EXEC' instead of the 'MOCK EXEC' I expected

令人好奇的是,如果我将两个exec方法重命名为othername,那么它可以正常工作:

Project proj = ProjectBuilder.builder().build()

proj.MetaClass.othername = { Closure obj ->
    println 'MOCK EXEC'
}

proj.othername {
    executable 'echo'
    args 'PROJECT EXEC'
}
// prints 'MOCK EXEC' as expected

我试图找出为什么现有的project.exec方法导致元编程失败以及是否有解决方法.请注意,Project是一个接口,但我正在模拟DefaultProject类型的特定实例.

用于截断单个方法的元编程方法来自这个答案:https://stackoverflow.com/a/23818476/1509221

解决方法

在Groovy中,使用MetaClass替换接口中定义的方法已被破坏.在这种情况下,exec方法在Project类中定义,它是一个接口.从 GROOVY-3493(最初在2009年报道):

"Cannot override methods via Metaclass that are part of an interface implementation"

替代方法

invokeMethod拦截所有方法并且可以工作.这有点矫枉过正,但确实有效.当方法名称与exec匹配时,它会将调用转移到mySpecialInstance对象.否则它将传递给委托,即现有方法.感谢invokeMethod delegationLogging All Methods对此的输入.

// This intercepts all methods,stubbing out exec and passing through all other invokes
this.project.MetaClass.invokeMethod = { String name,args ->
    if (name == 'exec') {
        // Call special instance to track verifications
        mySpecialInstance.exec((Closure) args.first())
    } else {
        // This calls the delegate without causing infinite recursion
        MetaMethod MetaMethod = delegate.class.MetaClass.getMetaMethod(name,args)
        return MetaMethod?.invoke(delegate,args)
    }
}

这很有效,除了你可能会看到关于“错误的参数数量”或“无法在null对象上调用方法xxxxx”的异常.问题是上面的代码不处理方法参数的强制转换.对于project.files(Object … paths),invokeMethod的args应该是[[‘path1′,’path2’]]的形式.但是,在某些情况下,调用文件(null)或文件(),因此invokeMethod的args分别为[null]和[],因为它预期[[]]失败.产生上述错误.

以下代码解决文件方法的问题,但这对我的单元测试来说已经足够了.我仍然想找到一种更好的强制类型或更换单一方法方法.

// As above but handle coercing of the files parameter types
this.project.MetaClass.invokeMethod = { String name,args ->
    if (name == 'exec') {
        // Call special instance to track verifications
        mySpecialInstance.exec((Closure) args.first())
    } else {
        // This calls the delegate without causing infinite recursion
        // https://stackoverflow.com/a/10126006/1509221
        MetaMethod MetaMethod = delegate.class.MetaClass.getMetaMethod(name,args)
        logInvokeMethod(name,args,MetaMethod)

        // Special case 'files' method which can throw exceptions
        if (name == 'files') {
            // Coerce the arguments to match the signature of Project.files(Object... paths)
            // Todo: is there a way to do this automatically,e.g. coerceArgumentsToClasses?
            assert 0 == args.size() || 1 == args.size()

            if (args.size() == 0 ||  // files()
                args.first() == null) {  // files(null)
                return MetaMethod?.invoke(delegate,[[] as Object[]] as Object[])
            } else {
                // files(ArrayList) possibly,so cast ArrayList to Object[]
                return MetaMethod?.invoke(delegate,[(Object[]) args.first()] as Object[])
            }
        } else {
            // normal pass through 
            return MetaMethod?.invoke(delegate,args)
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐