GroovyMock object on a final class doesn't work properly inside statically compiled service
Issue description
GroovyMock interactions for final class doesn't work inside@CompileStatic annotated class or in plain java class. In this case mock object will use real method implementation instead of provided interaction.
Minimal code to reproduce
import groovy.transform.CompileStatic
import spock.lang.Specification
class UserServiceSpec extends Specification {
def "test final class mock interactions"() {
setup:
def mock = GroovyMock(FinalClass) {
getName() >> "Foo Bar"
}
when:
new StaticService().getName(mock)
then:
thrown(UnsupportedOperationException)
expect:
new GroovyService().getName(mock) == "FOO BAR"
}
}
final class FinalClass {
String getName() {
throw new UnsupportedOperationException("Not implemented")
}
}
class GroovyService {
String getName(FinalClass obj) {
obj.name.toUpperCase()
}
}
@CompileStatic
class StaticService {
String getName(FinalClass obj) {
obj.name.toUpperCase()
}
}
Environment
Java: OpenJDK Runtime Environment (build 1.8.0_144-b01) Gradle: 3.5
Gradle dependencies
compile 'org.codehaus.groovy:groovy-all:2.4.12'
compile 'org.spockframework:spock-core:1.1-groovy-2.4'
compile 'cglib:cglib-nodep:3.2.5'
GroovyMocks work via modification of the meta-class. Using static compilation will basically turn groovy bytecode into java equivalent bytecode, e.g. static binding and thus no meta-class magic.
@leonard84
In this case GroovyMock works well when making FinalClass non-final. Also there is no matter whether CompileStatic over FinalClass is present or not.
@timic yes, I don't think there is anything we can do about it.
The bytecode generated for StaticService is the same whether FinalClass is indeed final or not, it uses invokevirtual in both cases. I think that the JVM is optimizing the code during load/execution, since the class is final, and inlines the address. However, I'm not an expert on this matter.
I have just encountered this problem as well!