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

Bug: Can't mock kotlinx.coroutines.channels.Channel #231

Open
3 tasks done
erikhuizinga opened this issue Jan 21, 2019 · 14 comments
Open
3 tasks done

Bug: Can't mock kotlinx.coroutines.channels.Channel #231

erikhuizinga opened this issue Jan 21, 2019 · 14 comments
Labels
bug ByteBuddy bug ByteBuddy issue important Kotlin bug bug in Kotlin language

Comments

@erikhuizinga
Copy link
Contributor

Prerequisites

Please answer the following questions for yourself before submitting an issue.

  • I am running the latest version
  • I checked the documentation and found no answer
  • I checked to make sure that this issue has not already been filed

Expected Behavior

I expect to be able to mock Channel from kotlinx.coroutines.channels.

Current Behavior

I get a java.lang.AbstractMethodError.

Failure Information (for bugs)

It seems that Channel cannot be mocked.

Steps to Reproduce

Run the code below. Fail.

Context

Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.

  • MockK version: 1.9
  • OS: macOS Mojave 10.14.2 (18C54)
  • Kotlin version: 1.3.11
  • JDK version: Android compile 27, target 27, min 14
  • JUnit version: 4.12
  • Type of test: unit test OR android instrumented test

Failure Logs

N/A

Stack trace

// -----------------------[ YOUR STACK STARTS HERE ] -----------------------
java.lang.AbstractMethodError: kotlinx.coroutines.channels.Channel$Subclass0.cancel()V

	at com.example.ChannelMockFailure$mocking a channel is impossible?$1.invoke(ChannelMockFailure.kt:15)
	at com.example.ChannelMockFailure$mocking a channel is impossible?$1.invoke(ChannelMockFailure.kt:11)
	at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:24)
	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.internalEvery(API.kt:92)
	at io.mockk.MockKKt.every(MockK.kt:104)
	at com.example.ChannelMockFailure.mocking a channel is impossible?(ChannelMockFailure.kt:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	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.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
// -----------------------[ YOUR STACK TRACE ENDS HERE ] -----------------------

Minimal reproducible code (the gist of this issue)

// -----------------------[ GRADLE DEFINITIONS ] -----------------------
dependencies {
	implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
	implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
	implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0'

	testImplementation 'junit:junit:4.12'

	androidTestImplementation 'com.android.support.test:runner:1.0.1'
	androidTestImplementation 'com.android.support.test:rules:1.0.1'

	testImplementation "io.mockk:mockk:1.9"
}
// -----------------------[ YOUR CODE STARTS HERE ] -----------------------
package com.example

import io.mockk.*
import kotlinx.coroutines.channels.Channel
import org.junit.Test

class ChannelMockFailure {
	@Test
	fun `mocking a channel is impossible?`() {
		val mock = mockk<Channel<Any?>>(name = "channel mock")
		every { mock.cancel() } just runs
		mock.cancel()
		verify { mock.cancel() }
	}
}
// -----------------------[ YOUR CODE ENDS HERE ] -----------------------
@erikhuizinga
Copy link
Contributor Author

erikhuizinga commented Jan 21, 2019

Probably related: spying on a channel is impossible (?) too:

package com.example

import io.mockk.*
import kotlinx.coroutines.channels.Channel
import org.junit.Test

class ChannelMockFailure {
	@Test
	fun `spying on a channel is impossible?`() {
		val spy = spyk(Channel<Any?>(), name = "channel spy")
		every { spy.cancel() } just runs
		spy.cancel()
		verify { spy.cancel() }
	}
}

@erikhuizinga
Copy link
Contributor Author

The spy fails with the following stacktrace:

java.lang.ClassCastException: kotlin.Unit cannot be cast to java.lang.Boolean

	at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:666)
	at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:660)
	at com.example.ChannelMockFailure.spying on a channel is impossible?(ChannelMockFailure.kt:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	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.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

@erikhuizinga
Copy link
Contributor Author

Wait..... the results are not deterministic. On some runs the aforementioned test methods pass, either one of them or both!

@oleksiyp
Copy link
Collaborator

Something weird is happening.

@oleksiyp
Copy link
Collaborator

I have no time to check it today

@oleksiyp
Copy link
Collaborator

Reproduced. Seems it is ByteBuddy issue, but not 100% sure now. Basically in some cases method close() is not present in a subclass.

image

So now it is time to make minimal reproducible code for ByteBuddy.

@erikhuizinga
Copy link
Contributor Author

Thanks for checking it out. I can't help you with that, so I hope you can reproduce whatever ByteBuddy magic is going on here! And then fix it or file an issue there. Good luck.

@oleksiyp
Copy link
Collaborator

Added ByteBuddy issue: raphw/byte-buddy#596

@oleksiyp
Copy link
Collaborator

oleksiyp commented Feb 1, 2019

Added KT ticket: https://youtrack.jetbrains.com/issue/KT-29658

@oleksiyp oleksiyp added the Kotlin bug bug in Kotlin language label Feb 10, 2019
@oleksiyp
Copy link
Collaborator

One more thing: Kotlin/kotlinx.coroutines#956

@oleksiyp oleksiyp changed the title Can't mock kotlinx.coroutines.channels.Channel Bug: Can't mock kotlinx.coroutines.channels.Channel Feb 10, 2019
@stale
Copy link

stale bot commented Jul 23, 2019

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.

@erikhuizinga
Copy link
Contributor Author

I've re-run the following test code many times (over 100 runs) and with MockK v1.9.3, Kotlin v1.3.50, Kotlin coroutines v1.3.2, JUnit v4.12, Android {target=28, min=16, compile=28}, it passed every time.

package com.example

import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.spyk
import io.mockk.unmockkAll
import io.mockk.verify
import kotlinx.coroutines.channels.Channel
import org.junit.After
import org.junit.AfterClass
import org.junit.Test

class ChannelMockFailure {
    companion object {
        @JvmStatic
        @AfterClass
        fun `after class`() = unmockkAll()
    }

    @After
    fun after() = clearAllMocks()

    @Test
    fun `mocking a channel is possible!`() {
        mockk<Channel<Any?>>(name = "channel mock") { verifyChannelCancellation() }
    }

    @Test
    fun `spying on a channel is possible!`() {
        spyk(Channel<Any?>(), name = "channel spy") { verifyChannelCancellation() }
    }

    private fun Channel<Any?>.verifyChannelCancellation() {
        every { cancel() } just runs
        cancel()
        verify { cancel() }
    }
}

I think this issue can be closed. Can you verify it works on your machine before closing, @oleksiyp?

@qwwdfsad
Copy link

The issue was fixed on kotlinx.coroutines side

@erikhuizinga
Copy link
Contributor Author

erikhuizinga commented Apr 20, 2020

@qwwdfsad in what version of the coroutines lib is this fix included?

Nvm, I found it: Kotlin/kotlinx.coroutines@27b8f45

It's in v1.3.5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug ByteBuddy bug ByteBuddy issue important Kotlin bug bug in Kotlin language
Projects
None yet
Development

No branches or pull requests

3 participants