It seems that @RequiredArgsConstructor is not working in the code below
- but only in a test using Spock framework, and only for a field which is of type of interface Dao
.
Strictly speaking - the code is working while it shouldn't work in my opinion, taking into account that similar test under JUnit5 doesn't compile at all.
Could someone explain is it a bug, or a feature ?
package brumba;
public interface Dao {
Integer getValueFor(Integer value);
}
package brumba;
import com.sun.istack.internal.NotNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class Brumba {
@NotNull
final private Dao dao;
// If you uncomment the below 2 lines, then the test fails
// @NotNull
// final private String name;
public Integer twice(Integer x){
return x * 2;
}
public Integer twiceDao(Integer x){
return dao.getValueFor(x);
}
}
The below code works fine - but only in Spock (a similar test under JUnit5 doesn't compile).
It seems that Spock test somehow sees a default no-args constructor (while JUnit test doesn't see this constructor)
But when the 2 commented lines above were uncommented, then the test failed with the below error:
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: brumba.Brumba()
package brumba
import spock.lang.Specification
class BrumbaTest extends Specification {
def "twice should multiply argument by 2"() {
given:
def testedObject = new Brumba();
expect:
y == testedObject.twice( x )
where:
x | y
0 | 0
1 | 2
2 | 4
3 | 6
}
}
And this JUnit test doesn't compile at all:
package brumba;
class BrumbaJUnit5Test {
@org.junit.jupiter.api.Test
void shouldTwice() {
Brumba br = new Brumba();
}
}
the error is:
Error:(7, 21) java: constructor Brumba in class brumba.Brumba cannot be applied to given types;
required: brumba.Dao,java.lang.String
found: no arguments
Here are the dependencies I am using for this project:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.0-M1</version>
<scope>test</scope>
</dependency>
First of all, I can confirm that this happens for me, too. I never noticed before.
I had to debug through source code and look at decompiled files in order to understand at least a bit what is going on here. I can tell you a few things:
int
), e.g. String
or your Dao
.Java class:
package de.scrum_master.stackoverflow;
public class Brumba {
public Brumba(String name) {}
}
Groovy class:
package de.scrum_master.stackoverflow
class BrumbaApp {
static void main(String[] args) {
new Brumba()
}
}
Decompiled Groovy class:
package de.scrum_master.stackoverflow;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class BrumbaApp implements GroovyObject {
public BrumbaApp() {
CallSite[] var1 = $getCallSiteArray();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].callConstructor(Brumba.class);
}
}
The Groovy runtime class CallSite
is actually an interface, but there is AbstractCallSite
implementing it. If we look at this method
public Object callConstructor(Object receiver) throws Throwable {
return callConstructor(receiver, CallSiteArray.NOPARAM);
}
and this definition
public final class CallSiteArray {
// ...
public static final Object [] NOPARAM = new Object[0];
// ...
we understand that actually this method will be called
public Object callConstructor(Object receiver, Object[] args) throws Throwable {
return CallSiteArray.defaultCallConstructor(this, receiver, args);
}
and so forth. I think what happens is that the Object[]
of size 0 will be passed through as a constructor parameter somehow and the absence of an element interpreted as a null
argument. This is also what you see in a debugger after object instantiation if like in your code the parameter is assigned to a member: The member will have the value null
.