Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.lang.NoClassDefFoundError: Could not initialize class androidx.lifecycle.LiveData$Subclass2 #5871

Closed
zoubarry opened this issue Aug 21, 2020 · 22 comments · Fixed by mockk/mockk#782

Comments

@zoubarry
Copy link

zoubarry commented Aug 21, 2020

Description

Tests were running fine on version 4.3.1 and breaking with 4.4-beta-1 with following stackTrace:

java.lang.NoClassDefFoundError: Could not initialize class androidx.lifecycle.LiveData$Subclass2
       at sun.reflect.GeneratedSerializationConstructorAccessor20.newInstance(Unknown Source)
       at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
       at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
       at io.mockk.proxy.jvm.ObjenesisInstantiator.instanceViaObjenesis(ObjenesisInstantiator.kt:75)
       at io.mockk.proxy.jvm.ObjenesisInstantiator.instantiateViaProxy(ObjenesisInstantiator.kt:66)
       at io.mockk.proxy.jvm.ObjenesisInstantiator.instance(ObjenesisInstantiator.kt:29)
       at io.mockk.proxy.jvm.ProxyMaker.instantiate(ProxyMaker.kt:75)
       at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:42)
       at io.mockk.impl.instantiation.JvmMockFactory.newProxy(JvmMockFactory.kt:34)
       at io.mockk.impl.instantiation.AbstractMockFactory.newProxy$default(AbstractMockFactory.kt:29)
       at io.mockk.impl.instantiation.AbstractMockFactory.temporaryMock(AbstractMockFactory.kt:127)
       at io.mockk.impl.recording.states.RecordingState$call$retValue$1.invoke(RecordingState.kt:72)
       at io.mockk.impl.instantiation.JvmAnyValueGenerator$anyValue$1.invoke(JvmAnyValueGenerator.kt:27)
       at io.mockk.impl.instantiation.AnyValueGenerator.anyValue(AnyValueGenerator.kt:27)
       at io.mockk.impl.instantiation.JvmAnyValueGenerator.anyValue(JvmAnyValueGenerator.kt:23)
       at io.mockk.impl.recording.states.RecordingState.call(RecordingState.kt:70)
       at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
       at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
       at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
       at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:20)

Steps to Reproduce

ViewModel:

    private val _data: MutableLiveData<...> = MutableLiveData()
    val data: LiveData<...> = _data

FragmentTest:

private val testData = MutableLiveData<...>()
private var viewModel: ViewModel = spyk(ViewModel()) {
        every { data } returns testData
}

running the test will give above stacktrace and error out

Robolectric & Android Version

Works fine on Robolectric 4.3.1, breaks with 4.4-beta-1
Android 28
mockk version 1.10.0

Link to a public git repo demonstrating the problem:

https://github.com/zoubarry/TestApp/blob/master/app/src/test/java/com/example/testapp/MainActivityTest.kt

@hoisie
Copy link
Contributor

hoisie commented Aug 21, 2020

One guess is that Robolectric's ClassLoader is acquiring androidx classes but not acquiring all mockk classes due to #5754. That may explain the visibility issues between MockK and Android classes.

To fix this you will probably have to tweak which MockK classes are acquired or not and make it more fine-grained. In order to do this you have to have pretty deep technical understanding of MockK and the agent mechanism.

I'd be happy to accept a PR to fix this but will unlikely have the time to investigate this myself.

In the meantime the workaround is to avoid mocking Android classes or try something like mockito-inline, which we have the ability to support better.

@hoisie
Copy link
Contributor

hoisie commented Aug 22, 2020

It's possible that not acquiring this but acquiring the other mockk classes may work:

https://github.com/mockk/mockk/blob/master/agent/jvm/src/main/java/io/mockk/proxy/jvm/dispatcher/JvmMockKDispatcher.java

@hoisie
Copy link
Contributor

hoisie commented Aug 24, 2020

Also can you put up a quick hello world app that demonstrates the issue? I haven't been able to repro it.

@zoubarry
Copy link
Author

@hoisie
Copy link
Contributor

hoisie commented Aug 28, 2020

I cloned that test app and had to add this to use binary resources to the app/build.gradle in the android block:

    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }

However, after adding that, the test passes on OSX with Java 11.

What OS/Java version are you using?

@zoubarry
Copy link
Author

@hoisie OSX with java 8.

@npike
Copy link

npike commented Nov 4, 2020

@hoisie We are also seeing this on a machine with openjdk 8, since our CI builds run in a Docker that uses openjdk13 we had to update our robolectric to 4.4 and mockk to 1.10.0.

Adding includeAndroidResources = true does not resolve this for the openjdk 8 machine, and everything runs as expected on machines with openjdk13.

@hoisie
Copy link
Contributor

hoisie commented Nov 4, 2020

In the spirit of transparency, this is not an issue that I will have time to investigate due to other priorities. If Robolectric/MockK (or any other third-party mocking library) integration is important, I would recommend that someone else root cause this issue and coordinates with the MockK author on a fix.

@kingargyle
Copy link
Contributor

@hoisie where might I start to see if I can address this issue, as I'm running into this with openJdk 11 on a M1 mac, in the past I was able to use an Intel Java 11 to get around the issue. If I can get this working I may be able to contribute back a patch.

@utzcoz
Copy link
Member

utzcoz commented Jan 24, 2022

@hoisie where might I start to see if I can address this issue, as I'm running into this with openJdk 11 on a M1 mac, in the past I was able to use an Intel Java 11 to get around the issue. If I can get this working I may be able to contribute back a patch.

What about checking #5871 (comment)? It's an example to reproduce this problem.

@kingargyle
Copy link
Contributor

@utzcoz I can reproduce the problem locally, I just need to know where in the code to look to fix:

#5754

Is probably where I'll start.

@utzcoz
Copy link
Member

utzcoz commented Jan 24, 2022

@utzcoz I can reproduce the problem locally, I just need to know where in the code to look to fix:

#5754

Is probably where I'll start.

After reading above comments, looks like #5754 cause the class visibility between mockK and Android classes(see #5871 (comment)). After @hoisie investigating, looks like Robolectric can remove acquiring https://github.com/mockk/mockk/blob/master/agent/jvm/src/main/java/io/mockk/proxy/jvm/dispatcher/JvmMockKDispatcher.java(see #5871 (comment)) only, instead of entire mockK package. Maybe it can a starting point. If dontAcquiring JvmMockKDispatcher doesn't work, you can dig into more to find the class list that needed to dontAcquiring to fix #5169.

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

@kingargyle out of curiosity why are you still using Java 8? This is not an issue with more recent Java versions, e.g. Java 11.

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

@kingargyle nvm, just read #5871 (comment), seems like you are having this issue on M1 + Java 11?

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

@kingargyle the repro app in this issue works fine for me using M1 + Java 11. Can you post the stack trace you are seeing? Is it the same stack trace as this?

@kingargyle
Copy link
Contributor

kingargyle commented Jan 24, 2022

@kingargyle the repro app in this issue works fine for me using M1 + Java 11. Can you post the stack trace you are seeing? Is it the same stack trace as this?

It is the same stack trace, this is with mockk 1.12.2, as well.

Only way I have gotten it to work locally is to use Mockito for the LiveData mocks, and mockk for everything else.

I'll need to get approval from work first before I try and do any PR to fix it.

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

java.lang.IllegalAccessError: failed to access class androidx.lifecycle.LiveData$ObserverWrapper from class androidx.lifecycle.LiveData$Subclass0

  • androidx.lifecycle.LiveData$ObserverWrapper is in unnamed module of loader org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader @de473ac
  • androidx.lifecycle.LiveData$Subclass0 is in unnamed module of loader net.bytebuddy.dynamic.loading.MultipleParentClassLoader @71313222)

Stack trace:

com.example.testapp.MainActivityTest > robo livedata errors FAILED
    java.lang.IllegalAccessError: failed to access class androidx.lifecycle.LiveData$ObserverWrapper from class androidx.lifecycle.LiveData$Subclass0 (androidx.lifecycle.LiveData$ObserverWrapper is in unnamed module of loader org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader @de473ac; androidx.lifecycle.LiveData$Subclass0 is in unnamed module of loader net.bytebuddy.dynamic.loading.MultipleParentClassLoader @71313222)
        at androidx.lifecycle.LiveData$Subclass0.<clinit>(Unknown Source)
        at jdk.internal.reflect.GeneratedSerializationConstructorAccessor12.newInstance(Unknown Source)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
        at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
        at io.mockk.proxy.jvm.ObjenesisInstantiator.instanceViaObjenesis(ObjenesisInstantiator.kt:75)
        at io.mockk.proxy.jvm.ObjenesisInstantiator.instantiateViaProxy(ObjenesisInstantiator.kt:66)
        at io.mockk.proxy.jvm.ObjenesisInstantiator.instance(ObjenesisInstantiator.kt:29)
        at io.mockk.proxy.jvm.ProxyMaker.instantiate(ProxyMaker.kt:75)
        at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:42)
        at io.mockk.impl.instantiation.JvmMockFactory.newProxy(JvmMockFactory.kt:34)
        at io.mockk.impl.instantiation.AbstractMockFactory.newProxy$default(AbstractMockFactory.kt:29)
        at io.mockk.impl.instantiation.AbstractMockFactory.temporaryMock(AbstractMockFactory.kt:127)
        at io.mockk.impl.recording.states.RecordingState$call$temporaryMock$1.invoke(RecordingState.kt:67)
        at io.mockk.impl.instantiation.JvmAnyValueGenerator$anyValue$2.invoke(JvmAnyValueGenerator.kt:35)
        at io.mockk.impl.instantiation.AnyValueGenerator.anyValue(AnyValueGenerator.kt:34)
        at io.mockk.impl.instantiation.JvmAnyValueGenerator.anyValue(JvmAnyValueGenerator.kt:31)
        at io.mockk.impl.recording.states.RecordingState.call(RecordingState.kt:73)
        at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
        at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:266)
        at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:23)
        at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:21)
        at com.example.testapp.MainViewModel.getData(MainViewModel.kt:13)
        at com.example.testapp.MainActivityTest$mockViewModel$1$1.invoke(MainActivityTest.kt:31)
        at com.example.testapp.MainActivityTest$mockViewModel$1$1.invoke(MainActivityTest.kt:24)
        at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:25)
        at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithRethrow$1.invoke(RecordedBlockEvaluator.kt:78)
        at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
        at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:40)
        at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
        at io.mockk.MockKDsl.internalEvery(API.kt:93)
        at io.mockk.MockKKt.every(MockK.kt:98)
        at com.example.testapp.MainActivityTest.<init>(MainActivityTest.kt:31)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
        at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:217)
        at org.robolectric.RobolectricTestRunner$HelperTestRunner.createTest(RobolectricTestRunner.java:577)
        at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:266)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:263)
        at org.robolectric.internal.SandboxTestRunner$HelperTestRunner.methodBlock(SandboxTestRunner.java:338)
        at org.robolectric.RobolectricTestRunner$HelperTestRunner.methodBlock(RobolectricTestRunner.java:586)
        at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:270)
        at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:88)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:829)

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

@kingargyle Because this was working in MockK 1.10.0, and not in MockK 1.12.2, the first step I would do is try to do a git bisect of MockK to try to find the PR or commit that introduced the regression.

At a high-level, the issue seems to be that the mock class that MockK defines (androidx.lifecycle.LiveData$Subclass0) is trying to access a class in the Robolectric class loader (androidx.lifecycle.LiveData$ObserverWrapper). Classes in parent classloaders do not have visibility into classes defined in their children.

Wild guess that may fix it: currently Robolectric does not acquire io.mockk.proxy into it's classloadder, and the instantiation happens in io.mockk.proxy.jvm.ObjenesisInstantiator. Perhaps that class or package could be excluded from being acquired, or do what @utzcoz suggested in #5871 (comment)?

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

To me this does seem to be more of an issue in MockK, though, because I would expect that the mock subclass of a class should be defined in the same classLoader as the original class.

@kingargyle
Copy link
Contributor

Looks like there is a new issue opened in Mockk: mockk/mockk#754

@hoisie
Copy link
Contributor

hoisie commented Jan 24, 2022

Commit that introduced the regression: mockk/mockk@62d5c79

hoisie added a commit to hoisie/mockk that referenced this issue Jan 24, 2022
This fixes an issue using MockK on classes defined in a Robolectric
class loader.

Fixes mockk#754
Partially fixes robolectric/robolectric#5871
@utzcoz
Copy link
Member

utzcoz commented Jun 11, 2023

@kingargyle @zoubarry Looks like v1.12.3 starts to fix mockk/mockk#754, see mockk/mockk#754 (comment). Please try this version of mockK, and feel free to reopen this issue if it still exists.

@utzcoz utzcoz closed this as completed Jun 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants