mockito / mockito

Most popular Mocking framework for unit tests written in Java
http://mockito.org
MIT License
14.88k stars 2.56k forks source link

[mockito-android] Mockito cannot mock this class on API >= 28 (in release build only) #2302

Open rusmonster opened 3 years ago

rusmonster commented 3 years ago

check that

mockito-android version: 3.10.0 reproduced on android devices: Pixel2 (arm-v8, API28), Pixel2(x86-Emulator, API28), Pixel2(x86-Emulator, API29)

After removing one of my instrumentation tests I started to get the following error on devices with API > 28 and only for release build (for debug build everything still working fine):

org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: interface com.twilio.conversations.app.repository.ConversationsRepository.

Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.

IMPORTANT INFORMATION FOR ANDROID USERS:

The regular Byte Buddy mock makers cannot generate code on an Android VM!
To resolve this, please use the 'mockito-android' dependency for your application:
https://search.maven.org/artifact/org.mockito/mockito-android

Java               : 0.9
JVM vendor name    : The Android Project
JVM vendor version : 2.1.0
JVM name           : Dalvik
JVM version        : 0.9
JVM info           : null
OS name            : Linux
OS version         : 4.4.111-g8b54bcf6207d

Underlying exception : java.lang.IllegalStateException: Cannot invoke BaseDexClassLoader#addDexPath(String, boolean)
at com.twilio.conversations.app.common.TestInjector.<init>(TestInjector.kt:117)
at com.twilio.conversations.app.common.TestInjectorKt.setupTestInjector(TestInjector.kt:35)
at com.twilio.conversations.app.ui.ConversationDetailsActivityTest$Companion.setupInjector(ConversationDetailsActivityTest.kt:172)
at com.twilio.conversations.app.ui.ConversationDetailsActivityTest.setupInjector(Unknown Source:2)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:154)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
Caused by: java.lang.IllegalStateException: Cannot invoke BaseDexClassLoader#addDexPath(String, boolean)
at net.bytebuddy.android.AndroidClassLoadingStrategy$Injecting$Dispatcher$ForAndroidPVm.loadDex(AndroidClassLoadingStrategy.java:779)
at net.bytebuddy.android.AndroidClassLoadingStrategy$Injecting.doLoad(AndroidClassLoadingStrategy.java:648)
at net.bytebuddy.android.AndroidClassLoadingStrategy.load(AndroidClassLoadingStrategy.java:145)
at net.bytebuddy.android.AndroidClassLoadingStrategy$Injecting.load(AndroidClassLoadingStrategy.java:641)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:100)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6292)
at org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator.mockClass(SubclassBytecodeGenerator.java:208)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.a(TypeCachingBytecodeGenerator.java:47)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.b(Unknown Source:0)
at a.a.i.c.a.h.call(Unknown Source:4)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:153)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:366)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:175)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:377)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClass(TypeCachingBytecodeGenerator.java:40)
at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMockType(SubclassByteBuddyMockMaker.java:77)
at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMock(SubclassByteBuddyMockMaker.java:43)
at org.mockito.android.internal.creation.AndroidByteBuddyMockMaker.createMock(AndroidByteBuddyMockMaker.java:39)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:53)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:84)
at org.mockito.Mockito.mock(Mockito.java:1954)
... 24 more
Caused by: java.lang.SecurityException: Can't exempt class, process is not debuggable.
at dalvik.system.DexFile.setTrusted(Native Method)
at dalvik.system.DexFile.setTrusted(DexFile.java:385)
at dalvik.system.DexPathList.makeDexElements(DexPathList.java:373)
at dalvik.system.DexPathList.addDexPath(DexPathList.java:226)
at dalvik.system.BaseDexClassLoader.addDexPath(BaseDexClassLoader.java:155)
at java.lang.reflect.Method.invoke(Native Method)
at net.bytebuddy.android.AndroidClassLoadingStrategy$Injecting$Dispatcher$ForAndroidPVm.loadDex(AndroidClassLoadingStrategy.java:770)
... 44 more

The ConversationsRepository interface which is cannot be mocked anymore:

interface ConversationsRepository {
    fun getUserConversations(): Flow<RepositoryResult<List<ConversationDataItem>>>
    fun getConversation(conversationSid: String): Flow<RepositoryResult<ConversationDataItem?>>
    fun getSelfUser(): Flow<User>
    fun getMessageByUuid(messageUuid: String): MessageDataItem?
    // Interim solution till paging v3.0 is available as an alpha version.
    // It has support for converting PagedList types
    fun getMessages(conversationSid: String, pageSize: Int): Flow<RepositoryResult<PagedList<MessageListViewItem>>>
    fun insertMessage(message: MessageDataItem)
    fun updateMessageByUuid(message: MessageDataItem)
    fun updateMessageStatus(messageUuid: String, sendStatus: Int)
    fun getTypingParticipants(conversationSid: String): Flow<List<ParticipantDataItem>>
    fun getConversationParticipants(conversationSid: String): Flow<RepositoryResult<List<ParticipantDataItem>>>
    fun updateMessageMediaDownloadStatus(
        messageSid: String,
        downloadId: Long? = null,
        downloadLocation: String? = null,
        downloading: Boolean? = null,
        downloadedBytes: Long? = null
    )
    fun updateMessageMediaUploadStatus(
        messageUuid: String,
        uploading: Boolean? = null,
        uploadedBytes: Long? = null
    )
    fun simulateCrash(where: CrashIn)
    fun clear()
    fun subscribeToConversationsClientEvents()
    fun unsubscribeFromConversationsClientEvents()
}

Full code of the removed test case:

@RunWith(AndroidJUnit4::class)
class ConversationsClientWrapperTest {

    private lateinit var context: Context
    private lateinit var conversationsClientWrapper: ConversationsClientWrapper

    @Before
    fun setUp() {
        context = InstrumentationRegistry.getInstrumentation().targetContext

        ConversationsClientWrapper.recreateInstance(context)
        conversationsClientWrapper = ConversationsClientWrapper.INSTANCE
    }

    @Test(expected = ConversationsException::class)
    fun create_withInvalidCredentials_returnsError() = runBlocking {
        conversationsClientWrapper.create(INVALID_IDENTITY, INVALID_PASSWORD)
    }
}

The full code of the project where issue reproduces in the mockito-issue-after branch: https://github.com/twilio/twilio-conversations-demo-kotlin/tree/bugfix/mockito-issue-after

To reproduce add any signingConfig into app/build.gradle for release build and run:

./gradlew app:connectedAndroidTest -DtestBuildType=release

failed build from the mockito-issue-after branch (commit 5255a58): https://app.circleci.com/pipelines/github/twilio/twilio-conversations-demo-kotlin/50/workflows/b56dd1b2-8df2-4617-9a0a-392484001b44

success build from the previous commit (2a5a7f3): https://app.circleci.com/pipelines/github/twilio/twilio-conversations-demo-kotlin/49/workflows/a7de3fe9-fbe7-420c-af13-25a17fbfbcce

Jacks0N23 commented 3 years ago

Same issue on latest(v3.12.4) version Help!

Jacks0N23 commented 3 years ago

My problem happened after upgrade to AGP 7.0, before everything worked fine

adenisyuk commented 1 year ago

Happened to me as well. Adding debuggable true to the testable build type fixed the error.

lilfry14 commented 1 year ago

is there anywork around with this? running with debuggable false is a significant time saving on running tests for us, so it'd be very inconvienent to need debuggable apks just to be able to use the latest mockito.