spock: Cannot create mock due to module java.base does not "opens java.lang.invoke"

Describe the bug

org.spockframework.mock.runtime.DefaultMethodInvoker is accessing java.lang.invoke.MethodHandles$Lookup.IMPL_LOOKUP which fails with:

org.spockframework.mock.CannotCreateMockException: Cannot create mock for class jdk.proxy2.$Proxy25Failed to invoke default method 'someDefaultMethod'

	at org.spockframework.mock.runtime.DefaultMethodInvoker.respond(DefaultMethodInvoker.java:64)
	at org.spockframework.mock.runtime.MockInvocation.callRealMethod(MockInvocation.java:59)
	at org.spockframework.mock.CallRealMethodResponse.respond(CallRealMethodResponse.java:30)
	at org.spockframework.mock.runtime.MockController.handle(MockController.java:50)
	at org.spockframework.mock.runtime.JavaMockInterceptor.intercept(JavaMockInterceptor.java:84)
	at org.spockframework.mock.runtime.DynamicProxyMockInterceptorAdapter.invoke(DynamicProxyMockInterceptorAdapter.java:34)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field static final java.lang.invoke.MethodHandles$Lookup java.lang.invoke.MethodHandles$Lookup.IMPL_LOOKUP accessible: module java.base does not "opens java.lang.invoke" to unnamed module @3d99d22e
	at org.spockframework.mock.runtime.DefaultMethodInvoker.respond(DefaultMethodInvoker.java:50)
	... 7 more

To Reproduce

This is a minimal reproducible example minimal-reproducible-example.zip

Expected behavior

The code should not fail.

Actual behavior

The code fails with the given exception.

Java version

openjdk version “17.0.1” 2021-10-19 LTS OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing) (and probably Java 16)

Buildtool version

Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d) Maven home: /Users/user/.sdkman/candidates/maven/current Java version: 17.0.1, vendor: Amazon.com Inc., runtime: /Users/user/.sdkman/candidates/java/17.0.1.12.1-amzn Default locale: en_US, platform encoding: UTF-8 OS name: “mac os x”, version: “12.0.1”, arch: “x86_64”, family: “mac”

What operating system are you using

Mac

Dependencies

com.example:demo:jar:0.0.1-SNAPSHOT
+- org.springframework.boot:spring-boot-starter:jar:2.6.2:compile
|  +- org.springframework.boot:spring-boot:jar:2.6.2:compile
|  |  \- org.springframework:spring-context:jar:5.3.14:compile
|  |     +- org.springframework:spring-aop:jar:5.3.14:compile
|  |     +- org.springframework:spring-beans:jar:5.3.14:compile
|  |     \- org.springframework:spring-expression:jar:5.3.14:compile
|  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.2:compile
|  +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.2:compile
|  |  +- ch.qos.logback:logback-classic:jar:1.2.9:compile
|  |  |  \- ch.qos.logback:logback-core:jar:1.2.9:compile
|  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.0:compile
|  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.0:compile
|  |  \- org.slf4j:jul-to-slf4j:jar:1.7.32:compile
|  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
|  +- org.springframework:spring-core:jar:5.3.14:compile
|  |  \- org.springframework:spring-jcl:jar:5.3.14:compile
|  \- org.yaml:snakeyaml:jar:1.29:compile
+- org.springframework.boot:spring-boot-starter-test:jar:2.6.2:test
|  +- org.springframework.boot:spring-boot-test:jar:2.6.2:test
|  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.6.2:test
|  +- com.jayway.jsonpath:json-path:jar:2.6.0:test
|  |  +- net.minidev:json-smart:jar:2.4.7:test
|  |  |  \- net.minidev:accessors-smart:jar:2.4.7:test
|  |  |     \- org.ow2.asm:asm:jar:9.1:test
|  |  \- org.slf4j:slf4j-api:jar:1.7.32:compile
|  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test
|  |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test
|  +- org.assertj:assertj-core:jar:3.21.0:test
|  +- org.hamcrest:hamcrest:jar:2.2:test
|  +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test
|  |  +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
|  |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
|  |  |  +- org.junit.platform:junit-platform-commons:jar:1.8.2:test
|  |  |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
|  |  \- org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test
|  +- org.mockito:mockito-core:jar:4.0.0:test
|  |  +- net.bytebuddy:byte-buddy:jar:1.11.22:test
|  |  +- net.bytebuddy:byte-buddy-agent:jar:1.11.22:test
|  |  \- org.objenesis:objenesis:jar:3.2:test
|  +- org.mockito:mockito-junit-jupiter:jar:4.0.0:test
|  +- org.skyscreamer:jsonassert:jar:1.5.0:test
|  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
|  +- org.springframework:spring-test:jar:5.3.14:test
|  \- org.xmlunit:xmlunit-core:jar:2.8.4:test
+- org.spockframework:spock-spring:jar:2.1-M2-groovy-3.0:test
|  +- org.spockframework:spock-core:jar:2.1-M2-groovy-3.0:test
|  |  \- org.junit.platform:junit-platform-engine:jar:1.8.2:test
|  \- org.codehaus.groovy:groovy:jar:3.0.9:test
+- org.codehaus.groovy:groovy-json:jar:3.0.9:test
\- org.codehaus.groovy:groovy-xml:jar:3.0.9:test

Additional context

Spock users can solve the issue by adding the following to the pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <argLine>
            --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
        </argLine>
    </configuration>
</plugin>

But I guess a permanent fix would be nice in the library.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 29 (27 by maintainers)

Commits related to this issue

Most upvoted comments

Java 17 offers a new method for default invocation.

This method can theoretically also be used from Byte Buddy (or cglib) proxies. Byte Buddy does however gracefully handle this when using a @SuperCall proxy or similar as long as the default method is not ambiguous. I’d suggest to only use the reflection hack if the above official approach is not available as a fallback.

You can Spy on interfaces, this is only useful if it has default methods that you don’t want to mock, but use as-is.

As for the other matter, I’m not aware of a way to fix this in a library. If you are using the module system, then you’ll have to open the base module accordingly.

Just one more bit of background information for your understanding, if you are interested in such details:

  • When creating an interface mock, Spock uses JDK proxies, as you can see class name jdk.proxy2.$Proxy25 from the error message.
  • JDK proxies are always final, hence you cannot subclass them, which would be necessary to mock or spy them.
  • JDK 16 introduced JEP 396 (Strongly Encapsulate JDK Internals by Default), therefore some internal APIs cannot be used without special Java CLI options anymore.

It looks as if in this case Spock is first creating an interface mock and then trying to spy on it. This feels wrong to me, if it proves to be true. What Spock could do instead is to either throw a comprehensive error message or force the creation of a Byte Buddy (or CGLIB) proxy in this case in order to avoid the error message. But actually, I think the use case is simply invalid and should have been forbidden all along.

I am leaving it up to a committer to answer the last question.