mockk icon indicating copy to clipboard operation
mockk copied to clipboard

Bug: Mocking method that returns coroutine Job fails in android instrumentation test

Open saied89 opened this issue 7 years ago • 12 comments

Mocking a method that returns Job fails in android instrumentation tests

Steps to Reproduce

I made an MWE for this issue. https://github.com/saied89/MockK_coroutine_Job_android_MWE

Please pay attention that while Instrumentation test and jvm test are identical, only jvm test runs to completion with instrumentation test throwing.

The failing mock:

val subject = mockk<Subject> {
            every { method() } returns Job()
        }
  • MockK version: 1.8.13.kotlin13 & 1.9
  • OS: Android
  • Kotlin version: 1.3.20
  • JUnit version: 4.12
  • Type of test: android instrumented test
// -----------------------[ YOUR STACK STARTS HERE ] -----------------------
java.lang.IllegalArgumentException: proxied interface methods have incompatible return types:
public abstract boolean kotlinx.coroutines.Job.cancel()
public abstract void kotlinx.coroutines.Job.cancel()
at java.lang.reflect.Proxy.validateReturnTypes(Proxy.java:328)
at java.lang.reflect.Proxy.getProxyClass(Proxy.java:167)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:232)
at io.mockk.proxy.common.ProxyMaker.proxy(ProxyMaker.kt:46)
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:124)
at io.mockk.impl.recording.states.RecordingState$call$retValue$1.invoke(RecordingState.kt:71)
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:69)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:54)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:272)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
at io.mockk.proxy.common.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt:36)
at android.saied.com.myapplication.Subject_Proxy.method(android/saied/com/myapplication/Subject_Proxy.generated)
at android.saied.com.myapplication.SubjectInstrumentationTest$methodTest$subject$1$1.invokeSuspend(SubjectTest.kt:16)
at android.saied.com.myapplication.SubjectInstrumentationTest$methodTest$subject$1$1.invoke(SubjectTest.kt)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invokeSuspend(RecordedBlockEvaluator.kt:26)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invoke(RecordedBlockEvaluator.kt)
at io.mockk.InternalPlatformDsl$runCoroutine$1.invokeSuspend(InternalPlatformDsl.kt:20)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:76)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:53)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at io.mockk.InternalPlatformDsl.runCoroutine(InternalPlatformDsl.kt:19)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2.invoke(RecordedBlockEvaluator.kt:26)
at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithNPERethrow$1.invoke(RecordedBlockEvaluator.kt:74)
at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:36)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:25)
at io.mockk.MockKDsl.internalCoEvery(API.kt:98)
at io.mockk.MockKKt.coEvery(MockK.kt:115)
at android.saied.com.myapplication.SubjectInstrumentationTest.methodTest(SubjectTest.kt:16)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
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.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
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.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:388)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1837)
// -----------------------[ YOUR STACK TRACE ENDS HERE ] -----------------------

Minimal reproducible code (the gist of this issue)

// -----------------------[ YOUR CODE STARTS HERE ] -----------------------
package android.saied.com.myapplication

import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.Job
import org.junit.Assert.*
import org.junit.Test

class SubjectInstrumentationTest {

    @Test
    fun methodTest() {
        val subject = mockk<Subject> {
            every { method() } returns Job()
        }
        subject.method()

        verify(exactly = 1) {
            subject.method()
        }
    }

}

open class Subject {
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)

    open fun method() = scope.launch { Log.d("", "") }
}
// -----------------------[ YOUR CODE ENDS HERE ] -----------------------

saied89 avatar Jan 26 '19 18:01 saied89

Thanks

oleksiyp avatar Jan 26 '19 23:01 oleksiyp

@oleksiyp any update on this? I'm facing the same issue with MockK: 1.9.1

razilsh avatar Mar 01 '19 06:03 razilsh

Hi, not yet

oleksiyp avatar Mar 01 '19 10:03 oleksiyp

@oleksiyp It seems to be working with JUnit5, if that helps.

razilsh avatar Mar 02 '19 09:03 razilsh

I ended up not using the single expression function Kotlin feature and wrapped the body in braces so that my function returned Unit.

On Sat, Mar 2, 2019 at 1:08 PM Razil [email protected] wrote:

@oleksiyp https://github.com/oleksiyp It seems to be working with JUnit5, if that helps.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/mockk/mockk/issues/237#issuecomment-468904539, or mute the thread https://github.com/notifications/unsubscribe-auth/ATMtNXgOetpwvQXYaTF-BpzS447NeyK-ks5vSkaFgaJpZM4aUTKg .

saied89 avatar Mar 02 '19 15:03 saied89

I can tell with 99% that this is an issue of incompatibility Kotlin bytecode with Java Reflection API. Next step is to raise Kotlin ticket and wait if it can be resolved

oleksiyp avatar Mar 09 '19 07:03 oleksiyp

I get the same error trying to mock Retrofit interfaces with methods that return Deferred<T>. However, it works with Mockito.

roccadev avatar Mar 15 '19 15:03 roccadev

I have a similar problem where I'm trying to capture a coroutine context, and I get the following error:

io.mockk.MockKException:
Class cast exception happened.
Probably type information was erased.
In this case use `hint` before call to specify exact return type of a method.
Caused by: java.lang.ClassCastException: kotlin.coroutines.CoroutineContext$Element$Subclass15 cannot be cast to kotlinx.coroutines.CoroutineId

The code is kinda big, but this is the relevant part:

val coroutineSlot = slot<suspend CoroutineScope.() -> Unit>()
every { stuffHere(arg1, arg2, capture(coroutineSlot)) } returns jobMockk

If you add something like .hint(CoroutineScope::class) the error changes to:

Caused by: java.lang.ClassCastException: kotlinx.coroutines.CoroutineScope$Subclass10 cannot be cast to kotlin.coroutines.CoroutineContext

(So CoroutineContext instead of CoroutineId)

Any tips for this? I am not able to .hint the relevant part as CoroutineId isn't accessible (internal data class) Is this a bug on mockk or in kotlin? @oleksiyp

Versions: Kotlin: 1.3 Kotlinx: 1.3.30 Java: 1.8 Mockk: 1.9.3 JUnit: 5.4.2

Jofairden avatar May 23 '19 07:05 Jofairden

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. If you are sure that this issue is important and should not be marked as stale just put an important tag.

stale[bot] avatar Jul 23 '19 18:07 stale[bot]

Any update about this issue!

Djermachi avatar Apr 20 '20 12:04 Djermachi

Update on this?

frizzkitten avatar Feb 19 '21 14:02 frizzkitten

Any update on this?

genericEventTracker = GenericEventTracker(get()) coEvery { hint(GenericEventTracker::class) genericEventTracker.trackAllEvents(any()) } returns Job()

I'm getting: Caused by: java.lang.ClassCastException: class com.textnow.android.events.GenericEventTracker cannot be cast to class kotlinx.coroutines.CoroutineScope (com.textnow.android.events.GenericEventTracker and kotlinx.coroutines.CoroutineScope are in unnamed module of loader org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader @55d10fc6)

techartist avatar Dec 10 '21 15:12 techartist