Bug: Mocking method that returns coroutine Job fails in android instrumentation test
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 ] -----------------------
Thanks
@oleksiyp any update on this? I'm facing the same issue with MockK: 1.9.1
Hi, not yet
@oleksiyp It seems to be working with JUnit5, if that helps.
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 .
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
I get the same error trying to mock Retrofit interfaces with methods that return Deferred<T>. However, it works with Mockito.
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
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.
Any update about this issue!
Update on this?
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)