I'd like to override the getProperty() method in a Spock test, per the documentation of get/setProperty. This is trivial in a normal Groovy class, but doesn't seem to work inside a Spock specification.
class MainSpec extends Specification {
#Override
def getProperty(String name) {
def value = metaClass.getProperty(this, name)
println "$name == $value"
return value
}
String foo = 'foo'
def test() {
expect:
foo
println foo
}
}
This example does not invoke the getProperty() method. It appears Spock is bypassing it somehow. Is there a way to hook into Spock's property resolution mechanism, or tell Spock to use my overridden method?
Yes, you can use "Expando" style, where the property should not be defined on the Spec class but is stored in a map (storage) and manipulated by setProperty and getProperty methods:
class MetaprogrammingSpec extends Specification {
def storage = [:]
#Override
def getProperty(String name) {
def value = storage[name]
println "$name == $value"
return value
}
#Override
void setProperty(String name, value) {
storage[name] = value
}
def test() {
when:
foo = 'bar'
then:
foo
println foo
}
}
Then the result is:
Update after comment: The getProperty() method is also called when you don't initialize the foo property. When you change the test in this way:
def test() {
expect:
foo
println foo
}
The result is as expected - getProperty() was called but test failes because foo is null:
It turns out that moving the property to a parent class is sufficient to make it pass through the getProperty() interceptor, even when the parent is another Specification class.
class BaseSpec extends Specification {
def foo = 'foo'
}
class MainSpec extends BaseSpec {
#Override
def getProperty(String name) {
def value = super.getProperty(name)
println "$name == $value"
return value
}
def test() {
expect:
foo
println foo
}
}
Related
I have the following code. I have an abstract JobParams, a class extending that abstract GradleJobParams, and a gjp variable with value using anonymous class declaration.
I want to test the overriding behavior of groovy. I can override the method setupRoot() but not the property testVar, why is that?
Tested on: https://groovyconsole.appspot.com/script/5146436232544256
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + testVar);
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
testVar:3,
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
The result is:
Starting
The testVar:1
Override root
Java (and thus Groovy) does not support overriding fields from the parent class with subclassing. Instead, it uses a mechanism called hiding fields:
Hiding Fields
Within a class, a field that has the same name as a field in the superclass hides the superclass's field, even if their types are different. Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super, which is covered in the next section. Generally speaking, we don't recommend hiding fields as it makes code difficult to read.
Source: https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html
It can be simply illustrated with the following example in Java:
final class SubclassHiddingFieldExample {
static abstract class A {
int value = 10;
void printValue1() {
System.out.println(value);
}
void printValue2() {
System.out.println(this.value);
}
void printValue3() {
System.out.println(((B)this).value);
}
}
static class B extends A {
int value = 12;
}
public static void main(String[] args) {
final B b = new B();
b.printValue1();
b.printValue2();
b.printValue3();
}
}
Output:
10
10
12
As you can see, only printValue3 prints out 3, because it cast this explicitly to B class.
Now, if you look at the decompiled bytecode of your JobParams class, you can see that the printTestVar method code is an equivalent of the following Java code:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
public abstract class JobParams implements GroovyObject {
private int root;
private Object testVar;
public JobParams() {
CallSite[] var1 = $getCallSiteArray();
byte var2 = 1;
this.testVar = Integer.valueOf(var2);
MetaClass var3 = this.$getStaticMetaClass();
this.metaClass = var3;
}
public Object setupRoot() {
CallSite[] var1 = $getCallSiteArray();
return var1[0].callCurrent(this, "The root");
}
public Object printTestVar() {
CallSite[] var1 = $getCallSiteArray();
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
}
public MetaClass getMetaClass() {
MetaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticMetaClass();
return this.metaClass;
}
}
public void setMetaClass(MetaClass var1) {
this.metaClass = var1;
}
public Object invokeMethod(String var1, Object var2) {
return this.getMetaClass().invokeMethod(this, var1, var2);
}
public Object getProperty(String var1) {
return this.getMetaClass().getProperty(this, var1);
}
public void setProperty(String var1, Object var2) {
this.getMetaClass().setProperty(this, var1, var2);
}
public int getRoot() {
return this.root;
}
public void setRoot(int var1) {
this.root = var1;
}
public Object getTestVar() {
return this.testVar;
}
public void setTestVar(Object var1) {
this.testVar = var1;
}
}
You can see that the line that prints out the value of the testVar field is represented by:
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
It means that no matter what value of testVar your subclass defines, the printTestVar method uses testVar field defined in the JobParams class. Period.
Using Groovy auto getter methods
There is one way you to implement the expected behavior. Every class field in Groovy has a getter method associated with that field compiled by Groovy for you. It means that you can access testVar by calling the getTestVar() method generated by the Groovy compiler. You can use it to override the value returned by a getter method for any field from the subclass. Consider the following example:
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + getTestVar()); // <-- using a getTestVar() method instead a testVar field
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
getTestVar: 3, // <-- stubbing getTestVar() method to return a different value
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
Output:
Starting
The testVar:3
Override root
My test code below:
#!/usr/bin/env groovy
class Inner {
def methodMissing(String name, args) {
println "inner:${name}"
}
}
def foo = {
bar()
}
foo.delegate = new Inner()
foo.call()
It gives result:
inner:bar
If I add another methodMissing method, like this:
#!/usr/bin/env groovy
class Inner {
def methodMissing(String name, args) {
println "inner:${name}"
}
}
// This method is added
def methodMissing(String name, args) {
println "outer:${name}"
}
def foo = {
bar()
}
foo.delegate = new Inner()
foo.call()
It gives result:
outer:bar
Why? I set the delegate to the closure 'foo'. Why isn't Inner. methodMissing called?
From this doc, I believe you'll want to change the resolution strategy. I have added a foo2 and some asserts to illustrate the contrast in behaviour:
class Inner {
def methodMissing(String name, args) {
println "inner:${name}"
return "inner:${name}"
}
}
def methodMissing(String name, args) {
println "outer:${name}"
return "outer:${name}"
}
def foo = { bar() }
foo.delegate = new Inner()
assert "outer:bar" == foo.call()
def foo2 = { bar() }
foo2.delegate = new Inner()
foo2.resolveStrategy = Closure.DELEGATE_FIRST
assert "inner:bar" == foo2.call()
Some time ago I asked how to make a class implement an interface that it doesn't implement by declaration.
One possibility is as-coercion:
interface MyInterface {
def foo()
}
class MyClass {
def foo() { "foo" }
}
def bar() {
return new MyClass() as MyInterface
}
MyInterface mi = bar()
assert mi.foo() == "foo"
Now that I try to use it, I don't know what interface is needed at compile-time. I tried using generics like this:
interface MyInterface {
def foo()
}
class MyClass {
def foo() { "foo" }
}
class Bar {
public static <T> T bar(Class<T> type) {
return new MyClass() as T
}
}
MyInterface mi = Bar.bar(MyInterface.class)
assert mi.foo() == "foo"
But it raises the following exception:
Cannot cast object 'MyClass#5c4a3e60' with class 'MyClass' to class 'MyInterface'
How can I coerce to an interface that is only known at run-time?
interface MyInterface {
def foo()
}
interface AnotherInterface {
def foo()
}
class MyClass {
def foo() { "foo" }
}
class Bar {
static <T> T bar(Class<T> type) {
new MyClass().asType(type)
}
}
MyInterface mi = Bar.bar(MyInterface)
assert mi.foo() == "foo"
AnotherInterface ai = Bar.bar(AnotherInterface)
assert ai.foo() == "foo"
I have a class that implements GroovyInterceptable, and I thought it should make all method invocation to go through the 'invokeMethod'. But I find that this is not the case.
class Test implements GroovyInterceptable{
String name
#Override
Object invokeMethod(String name, Object args) {
def metaMethod = this.metaClass.getMetaMethod(name, args)
return metaMethod.invoke(this, "BAR")
}
public static void main(String[] args) {
Test.metaClass.NEWATTRIBUTE = null
def test = new Test()
test.NEWATTRIBUTE = "MEOW1"
println test.NEWATTRIBUTE // shouldnt this be BAR ?
test.setNEWATTRIBUTE("MEOW2")
println test.NEWATTRIBUTE // prints 'BAR'
// much simpler example ....
test.name = "MOOO"
println test.name // shouldnt this be BAR ?
test.setName("MOOO")
println test.name // prints 'BAR'
}
}
I think I read somewhere, that
test.NEWATTRIBUTE = "MEOW1"
doesnt really access the field directly. Groovy will still call the setter method - and hence the invokeMethod should get invoked, shouldnt it?
Thanks
Alex
When setting a property, Groovy invokes setProperty. Add this method to the class:
void setProperty(String prop, val) {
System.out.println "set property $prop with $val"
}
WillP: Thanks a lot. Here is the complete code:
class Test implements GroovyInterceptable{
String name
#Override
Object invokeMethod(String name, Object args) {
def metaMethod = this.metaClass.getMetaMethod(name, args)
return metaMethod.invoke(this, "BAR")
}
void setProperty(String prop, val) {
getMetaClass().setProperty(this, prop, "BAR2");
}
public static void main(String[] args) {
Test.metaClass.NEWATTRIBUTE = null
def test = new Test()
test.NEWATTRIBUTE = "MEOW1"
println test.NEWATTRIBUTE // prints BAR2
test.setNEWATTRIBUTE("MEOW2")
println test.NEWATTRIBUTE // prints BAR
test.name = "MOOO"
println test.name // prints BAR2
test.setName("MOOO")
println test.name // prints BAR
}
}
as it turns out, setProperty() is called regardless the class implements GroovyInterceptable or not. But invokeMethod() is only called when the class implements GroovyInterceptable
Is there a way to replace using the metaclass object, a method that is of a super class.
Example:
class A {
def doIt(){
two()
println 'do it!'
}
protected two(){
println 'two'
}
}
class B extends A{
def doLast(){
doIt()
}
}
B b = new B();
b.doIt()
/*
* two
* doit!
*/
b.metaClass.two = {
println 'my new two!'
}
b.doIt();
/*
* my new two!
* do it!
*/
Since two and doIt are declared in the same class, groovy will skip the meta-object protocol for this call. You can override this behavior by marking the super class as GroovyInterceptable, which forces all method calls to go through invokeMethod. For example:
class A implements GroovyInterceptable {
def doIt(){
two()
println 'do it!'
}
protected two(){
println 'two'
}
}
class B extends A {
def doLast(){
doIt()
}
}
B b = new B()
b.doIt() // prints two
b.metaClass.two = {
println 'my new two!'
}
b.doIt() // prints my new two!