@SpykBean on Spring Data Repository doesn't work anymore with JDK16+

See original GitHub issue

Hi,

I already stumbled over @jnizet commit https://github.com/Ninja-Squad/springmockk/commit/b6dbc1b50d4cacf72c50119fdfc5273ae5fc4677

But in our case @SpykBean isn’t working with following configuration:

  • openjdk version “16.0.1” 2021-04-20
  • Kotlin 1.5.21
  • Spring Boot 2.5.3
  • SpringMockk 3.0.1

Simple data repository spy:

    @SpykBean
    private lateinit var documentRepository: DocumentRepository

Exception when running the test:

...
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'documentRepository': Post-processing of FactoryBean's singleton object failed; nested exception is java.lang.IllegalAccessException: class io.mockk.impl.InternalPlatform$copyFields$1 cannot access a member of class java.lang.reflect.Proxy (in module java.base) with modifiers "protected"
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
	... 68 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'documentRepository': Post-processing of FactoryBean's singleton object failed; nested exception is java.lang.IllegalAccessException: class io.mockk.impl.InternalPlatform$copyFields$1 cannot access a member of class java.lang.reflect.Proxy (in module java.base) with modifiers "protected"
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:119)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1884)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1266)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:345)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 87 common frames omitted
Caused by: java.lang.IllegalAccessException: class io.mockk.impl.InternalPlatform$copyFields$1 cannot access a member of class java.lang.reflect.Proxy (in module java.base) with modifiers "protected"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:687)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1096)
	at java.base/java.lang.reflect.Field.get(Field.java:417)
	at io.mockk.impl.InternalPlatform$copyFields$1.invoke(InternalPlatform.kt:110)
	at io.mockk.impl.InternalPlatform$copyFields$1.invoke(InternalPlatform.kt:114)
	at io.mockk.impl.InternalPlatform.copyFields(InternalPlatform.kt:117)
	at io.mockk.impl.instantiation.AbstractMockFactory.spyk(AbstractMockFactory.kt:107)
	at com.ninjasquad.springmockk.SpykDefinition.createSpy(SpykDefinition.kt:75)
	at com.ninjasquad.springmockk.MockkPostProcessor.createSpyIfNecessary(MockkPostProcessor.kt:335)
	at com.ninjasquad.springmockk.MockkPostProcessor$SpyPostProcessor.postProcessAfterInitialization(MockkPostProcessor.kt:398)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:437)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1929)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116)
	... 96 common frames omitted

The workaround is to switch to JDK <= 15.

Has anyone an idea to fix the issue?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

5reactions
phillipuniversecommented, Feb 25, 2022

@jnizet I agree that this is almost certainly a mockk issue with proxy spies.

Another piece of information - for whatever reason the final exception masks the original exception:

java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @17550481
image

So it appears the new Java 17 strong encapsulation is what is actually breaking this. In 9-11 this just gave a warning, now it fails hard.

@ajgassner @PavelPolyakov a workaround is to add the --add-opens JVM flag to your test execution. In Gradle (build.gradle.kts):

tasks.test {
    jvmArgs = listOf("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED")
}

Adding this for me causes my spied repositories to work just as before. My JVM:

openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode, sharing)
2reactions
PavelPolyakovcommented, Jan 17, 2022

Also met this issue while trying to update to JDK17.

Are there any options to overcome this? I thought common to spy on repositories. I’m surprised that not that many people report that they have this problem. I don’t see any relevant issue on mockk side.

One option I see is not to spy on the repositories 😉 But it’s said nowhere in the documentation, that one shall not do that.

Read more comments on GitHub >

github_iconTop Results From Across the Web

@SpyBean does not work when used to spy on a Spring Data ...
Version: Spring Boot 1.4.1 Subject: @SpyBean on Data Jpa Repository bean isn't working Exception thrown: UnsatisfiedDependencyException: ...
Read more >
Mockito cannot create Spy of @Autowired Spring-Data ...
@SpyBean now works with spring data repositories since spring boot ... var yourSpy = Mockito.mock(FooRepository.class, AdditionalAnswers.
Read more >
Spring Boot Reference Documentation
Try the How-to documents. They provide solutions to the most common questions. Learn the Spring basics. Spring Boot builds on many other Spring...
Read more >
Spring Boot and Java 16 Records - Ashish Choudhary
I have a working Spring Boot application that I will use to demo Java records with it.
Read more >
Using Spring Boot @SpyBean - Shekhar Gulati
The reason test threw NullPointerException is that Spring uses MongoTemplate internally to provide implementation of your repository interfaces.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found