当我们对使用someClass.metaClass.constructor
注释的类的方法中可用的任何特定类(如RESTClient
)使用@CompileStatic
时,构造函数重写根本不起作用。
当我们移除@CompileStatic
注释时,它正常工作。我是不是遗漏了什么?
样本代码:
@CompileStatic
class FooClass {
String getDataFromProvider() {
String url = "https://www.example.com"
RESTClient restClient = new RESTClient(url)
HttpResponseDecorator response = restClient.post([:]) as HttpResponseDecorator
return response
}
}
而测试用例:
import groovyx.net.http.HttpResponseDecorator
import groovyx.net.http.RESTClient
import spock.lang.Specification
class FooContentSpec extends Specification {
void "test getDataFromProvider method"() {
given: "Rest url"
String restURL = "https://www.example.com"
and: "Mock RESTClient"
RESTClient mockedRestClient = Mock(RESTClient)
// THIS IS NOT WORKING
RESTClient.metaClass.constructor = { Object url ->
assert restURL == url
return mockedRestClient
}
mockedRestClient.metaClass.post = { Map<String, ?> args ->
return ""
}
when: "We hit the method"
HttpResponseDecorator response = Content.getDataFromProvider()
then: "We should get status 200"
response.statusCode == 200
}
}
根据花式郎文档:
MockFor
和StubFor
不能用于测试静态编译的类,例如用于使用@CompileStatic
的Java类或Groovy类。要对这些类进行存根和/或模拟,可以使用Spock或Java模拟库之一。
预期行为
在这个场景中,RESTClient
的构造函数重写应该在我们的测试用例中工作,因为我们不想在每个测试用例中使用第三方API。
实际行为
不幸的是,RESTClient
没有被嘲弄,因为它每次都会碰到@CompileStatic
注释。
环境信息
------------------------------------------------------------
Gradle 3.5
------------------------------------------------------------
Groovy: 2.4.10,
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015,
JVM: 1.8.0_221 (Oracle Corporation 25.221-b11),
OS: Mac OS X 10.15.2 x86_64
发布于 2019-12-25 19:24:42
您是对的,@CompileStatic
不能与元类操作结合使用。原因是,顾名思义,所有这些都是在编译时解析和绑定的,因此没有元类查找,因此无法覆盖它。
我建议查看IoC/dependency,这样您就可以将模拟注入到代码中。使用经典的单元素会使您的代码更难测试。
发布于 2019-12-27 05:15:36
在伦纳德·布吕宁斯的评论之后
是的,@CompileStatic将在编译时解析RESTClient在您的FooClass中的构造函数,因此它不会在运行时使用元类来锁定它。如果您想看看它的外观,我建议使用反编译器,例如bytecode查看器,并查看生成的字节码。
我们将生成的字节码解压缩为两种情况:
用@CompileStatic
public class FooClass implements GroovyObject {
public FooClass() {
MetaClass var1 = this.$getStaticMetaClass();
this.metaClass = var1;
}
public String getDataFromProvider() {
String url = "https://www.example.com";
// Directly constructor is getting used
RESTClient restClient = new RESTClient(url);
HttpResponseDecorator response = (HttpResponseDecorator)ScriptBytecodeAdapter.asType(restClient.post(ScriptBytecodeAdapter.createMap(new Object[0])), HttpResponseDecorator.class);
return (String)ShortTypeHandling.castToString(response);
}
}
无@CompileStatic
public class FooClass implements GroovyObject {
public FooClass() {
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public String getDataFromProvider() {
CallSite[] var1 = $getCallSiteArray();
String url = "https://www.example.com";
// Here Groovy's metaprogramming is into play instead of directly calling constructor
RESTClient restClient = (RESTClient)ScriptBytecodeAdapter.castToType(var1[0].callConstructor(RESTClient.class, url), RESTClient.class);
HttpResponseDecorator response = (HttpResponseDecorator)ScriptBytecodeAdapter.asType(var1[1].call(restClient, ScriptBytecodeAdapter.createMap(new Object[0])), HttpResponseDecorator.class);
return (String)ShortTypeHandling.castToString(response);
}
}
所以伦纳德的回答是完全正确的。我们忽略了这个简单的Java概念。
https://stackoverflow.com/questions/59476058
复制相似问题