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: exception while calling mocked suspend function #288

Open
gladed opened this issue Apr 20, 2019 · 29 comments
Open

Bug: exception while calling mocked suspend function #288

gladed opened this issue Apr 20, 2019 · 29 comments

Comments

@gladed
Copy link

gladed commented Apr 20, 2019

In Version 1.9.3, calling a mocked suspending function throws "no answer found" even if an answer was provided.

val call = mockk<suspend () -> Int>()
coEvery { call() } returns 5
runBlocking { assertEquals(5, call()) } // throws MockKException below
  • This works if the mocked function does not suspend.
  • A workaround is to wrap the suspending function in an interface (but it would be cleaner to be able to mock the suspending function directly.)

Here's the exception:

io.mockk.MockKException: no answer found for: Function1(#8).invoke(continuation {})
	at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
	at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
	at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
	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)
	at io.mockk.proxy.jvm.advice.BaseAdvice.handle(BaseAdvice.kt:42)
	at io.mockk.proxy.jvm.advice.jvm.JvmMockKProxyInterceptor.interceptNoSuper(JvmMockKProxyInterceptor.java:45)
	at kotlin.jvm.functions.Function1$Subclass3.invoke(Unknown Source)
	at ScopingStoreTest$put an item$1$2.invokeSuspend(ScopingStoreTest.kt:56)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
	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 MyTest$myTest$1.invokeSuspend(MyTest.kt:56)
        ...
@oleksiyp
Copy link
Collaborator

Seems it is a bit tricky. And I think it was working before.

Got following message:

java.lang.UnsupportedOperationException: This class is an internal synthetic class generated by the Kotlin compiler, such as an anonymous class for a lambda, a SAM wrapper, a callable reference, etc. It's not a Kotlin class or interface, so the reflection library has no idea what declarations does it have. Please use Java reflection to inspect this class: class io.mockk.gh.Issue288Test$suspendLambdaShouldBeMockable$x$1

for this code that mimics what mockk does:

        val x: suspend () -> Int = { 5 }
        x::class.java.methods.first { it.name == "invoke" }.kotlinFunction

@oleksiyp
Copy link
Collaborator

I found some workaround - check if the actual last argument is a continuation, not just method types. Sad that more and more workarounds are needed, but I think anyway it has it's value.

@PaulWoitaschek
Copy link

I was injecting a suspending function through the constructor. A workaround is to declare a regular interface with an invoke function and pass a method reference to the object under test. Then the call side for the actual mocking stays identically.

So for example my class under test had a function declared in the constructor:

private val api: suspend (Key) -> Value

As a workaround instead of mocking the suspending function

private val api : suspend (Int) -> String = mockk()

I created an interface:

private interface TestApi<Key,Value> {
  suspend operator fun invoke(key: Key): Value
}

and I'm passing a method reference:

private val api: TestApi<Int,String> = mockk()
val repo = Repository(
  api = api::invoke
)

Now at the mocking side the usage stays the same:

coEvery { api(key) } returns freshValue

@stale
Copy link

stale bot commented Aug 30, 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 ask to put an important label.

@stale stale bot added the stale label Aug 30, 2019
@PaulWoitaschek
Copy link

Please put an important label.

@stale stale bot removed the stale label Aug 30, 2019
@oleksiyp oleksiyp changed the title mocked suspending function does not answer Bug: exception while calling mocked suspend function Nov 1, 2019
@oleksiyp oleksiyp added this to the 1.9.4 milestone Nov 1, 2019
@oleksiyp oleksiyp added this to To do in Critical to fix Nov 2, 2019
@DevSrSouza
Copy link

DevSrSouza commented Nov 17, 2019

I think I am getting the same thing

    @Test
    fun `should listen to publish of a String`() {
        val localEventScope = LocalEventScope()

        val onReceiveMock = mockk<suspend (String) -> Unit>()
        coEvery { onReceiveMock.invoke(any<String>()) } returns Unit

        localEventScope.listen<String>(this, Dispatchers.Default, onReceiveMock)

        localEventScope.publish("test string")

        coVerify { onReceiveMock.invoke(any()) }
    }
Exception in thread "Test worker @coroutine#4" io.mockk.MockKException: no answer found for: Function2(#1).invoke(test string, continuation {})
	at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
	at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
	at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
	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)
	at io.mockk.proxy.jvm.advice.BaseAdvice.handle(BaseAdvice.kt:42)
	at io.mockk.proxy.jvm.advice.jvm.JvmMockKProxyInterceptor.interceptNoSuper(JvmMockKProxyInterceptor.java:45)
	at kotlin.jvm.functions.Function2$Subclass0.invoke(Unknown Source)
	at br.com.devsrsouza.eventkt.scopes.LocalEventScope$publish$1.invokeSuspend(LocalEventScope.kt:11)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:457)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:154)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
	at br.com.devsrsouza.eventkt.scopes.LocalEventScope.publish(LocalEventScope.kt:9)
	at br.com.devsrsouza.eventkt.LocalEventScopeTest.should listen to publish of a String(LocalEventScopeTest.kt:19)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	....

@gabrielprado-mb
Copy link

Any news about this issue?

@mkruczkowski
Copy link

I have also experienced this, is there a chance to fix it?

@kdotdi
Copy link

kdotdi commented Mar 27, 2020

@gladed Could you please explain what do you exactly mean by

wrap the suspending function in an interface

?

@PaulWoitaschek
Copy link

@kdotdi See my post which shows an example for that.

@gladed
Copy link
Author

gladed commented Mar 27, 2020

interface Suspender {
    suspend fun foo(): Int
}

@Test
fun suspendMockTest() {
    val happyMock = mockk<Suspender>()
    coEvery { happyMock.foo() } returns 5
    runBlocking { assertEquals(5, happyMock.foo()) } // Pass

    val sadMock = mockk<suspend () -> Int>()
    coEvery { sadMock() } returns 5
    runBlocking { assertEquals(5, sadMock()) } // throws MockKException
}

@oleksiyp oleksiyp moved this from To do to Done in Critical to fix Apr 19, 2020
oleksiyp added a commit that referenced this issue Apr 19, 2020
@imGurpreetSK
Copy link

I'm getting the same error for the following code:

flow { emit(httpClient.get<String>(UrlMap.getUrl(UrlMap.trendingRepositories))) }
                .take(1)
                .map { Json.nonstrict.parse(TrendingRepositoriesResponse.serializer(), it).results }
                .map { FetchEvent(FetchAction.FETCH_SUCCESS, it) }
                .catch {
                    emit(
                        FetchEvent(
                            FetchAction.FETCH_FAILED,
                            null,
                            listOf(it.toApplicationError())
                        )
                    )
                }
                .flowOn(schedulers.io())

This is the failing test:

    @Test
    fun `fetching repositories succeeds - cache failure, remote success`() {
        val expectedEvents = listOf(
            FetchEvent<List<TrendingRepository>>(FetchAction.IN_FLIGHT, null),
            FetchEvent(FetchAction.FETCH_SUCCESS, getTrendingRepos())
        )
        coEvery { httpClient.get<String>(UrlMap.getUrl(UrlMap.trendingRepositories)) }
            .returns(
                Json.stringify(
                    TrendingRepositoriesResponse.serializer(),
                    TrendingRepositoriesResponse(getTrendingRepos())
                )
            )

        val emittedEvents = mutableListOf<FetchEvent<List<TrendingRepository>>>()
        runBlocking {
            repo
                .fetchMostPopularMovies()
                .toCollection(emittedEvents)
        }

        expectedEvents.assertEqualTo(emittedEvents)
    }

@p1415604x
Copy link

Cannot really use this workaround @gladed is providing. I am mocking object that is injected via DI and that object's suspend method has to return value. I cannot wrap anything within interfaces as it would mess up the production code.

@PaulWoitaschek
Copy link

@p1415604x doesn't my workaround work?

@renukame
Copy link

renukame commented Sep 1, 2020

In version 1.10.0 i am still getting the same error Exception in thread "main @coroutine#5" io.mockk.MockKException: no answer found for:

I am using Room db and calling its dao methods which are suspended

Example

interface TestDao{
suspend fun getValue():String{}
}

class Test{
fun test(){
Globalscope.launch(Dispatchers.IO)
{
val value = db.testDao.getValue()}}

}

@test
class TestClass{
val testDao= mockk()
coEvery { db.testDao() } returns testDao
coEvery { testDao.getValue("k1", "s1") } return ""
}

@Bwaim
Copy link

Bwaim commented Jan 15, 2021

Any news on this bug ?
I'm having the same bug mocking an object having a suspend function. Any workaround for this case ?

@zakrodionov
Copy link

The same error. CoEvery works only if you transfer it to setup.

@TheReprator
Copy link

Any update on this issue?

@Gozde027
Copy link

Gozde027 commented Nov 8, 2021

I'm having the same issue, any update?

@Tgo1014
Copy link

Tgo1014 commented Jan 6, 2022

Also having this same issue

@hiteshchopra11
Copy link

Any update? facing same issue

@oalpayli
Copy link

oalpayli commented Feb 3, 2022

Any update? same here

@pvegh
Copy link

pvegh commented Mar 14, 2022

3rd birthday coming up, let's throw a party. Who brings cake?
It's marked with Critical, Important, and Bug, better to remove those labels, looks like many users have been waiting for a fix all this time.

@napolux
Copy link

napolux commented Mar 21, 2022

3rd birthday coming up, let's throw a party. Who brings cake? It's marked with Critical, Important, and Bug, better to remove those labels, looks like many users have been waiting for a fix all this time.

This is not how you should ask for an update on a github issue. Take notes, kids.

@PaulWoitaschek
Copy link

Instead of taking notes I suggest to submit a PR instead of complaing 😊

@melihaksoy
Copy link

melihaksoy commented Apr 1, 2022

I don't know if it'll be of help, but I get this error when I start using mocked or spied values instead of constants in a coEvery.

Class being tested is storeRepository so it's not a mock.

Working:

        val testCode = "testCode"
        val testLanguage = "testLanguage"
        val parameters : Parameters = mockk()
        
        every { parameters.code } returns testCode
        every { parameters.language } returns testLanguage

        // Note that it doesn't use `parameters` but constants directly
        coEvery {
            storeRepository.getStores(
                testCode,
                testLanguage
            )
        } returns testStoreList

This is also working:

        val parameters : Parameters = Parameters.TestParameters

        coEvery {
            storeRepository.getStores(
                parameters.code,
                parameters.language
            )
        } returns testStoreList

This one ends up with error in this issue:

        val parameters : Parameters = mockk() // or spyk(Parameters.TestParameters)
        
        // No need for these if using spyk
        every { parameters.code } returns testCode
        every { parameters.language } returns testLanguage

        coEvery {
            storeRepository.getStores(
                parameters.code,
                parameters.language
            )
        } returns testStoreList

I don't know the internals of the lib, but looks like fields of mocked or spied properties somehow are not matching inside coEvery.

Update

It seems something similar happens for verify / coVerify.

This verification works:

            val parameters : Parameters = mockk()
	        every { parameters.code } returns testCode
        	every { parameters.language } returns testLanguage

            coVerify(exactly = 3) { parameters.code }
            coVerify(exactly = 3) { parameters.language }

            coVerify(exactly = 1) {
                storeService.getStoreListAsync(
                    testCode,
                    testLanguage
                )
            }

But when I change it to this:

			val parameters : Parameters = mockk()
	        every { parameters.code } returns testCode
        	every { parameters.language } returns testLanguage

            coVerify(exactly = 3) { parameters.code }
            coVerify(exactly = 3) { parameters.language }

            coVerify(exactly = 1) {
                storeService.getStoreListAsync(
                    parameters.code,
                    parameters.language
                )
            }

both coVerify(exactly = 3) { parameters.code } and coVerify(exactly = 3) { parameters.language } fails, they start to be treated as exactly = 1 somehow 🤔 Let me know if this should be a separate issue !

@thaibt
Copy link

thaibt commented Apr 5, 2022

Hello, no contribution here - just following with the same issue.

@MockK
lateinit var sqsQueueClient : SQSClient

lateinit var sqsQueue : SQSQueue

@BeforeEach
fun setUp() {
    MockKAnnotations.init(this)
    sqsQueue = SQSQueue(sqsQueueClient)
}

@Test
fun testDeleteSingleNotification() = runTest {
    coEvery {  sqsQueueClient.deleteMessage(any<Message>()) } returns true

    sqsQueue.deleteNotifications(emptyList())
}

io.mockk.MockKException: no answer found for: SQSClient(sqsQueueClient#1).deleteMessages([Message(Body=, Attributes={ApproximateReceiveCount=0})], continuation {})

@thaibt
Copy link

thaibt commented Apr 5, 2022

interface Suspender {
    suspend fun foo(): Int
}

@Test
fun suspendMockTest() {
    val happyMock = mockk<Suspender>()
    coEvery { happyMock.foo() } returns 5
    runBlocking { assertEquals(5, happyMock.foo()) } // Pass

    val sadMock = mockk<suspend () -> Int>()
    coEvery { sadMock() } returns 5
    runBlocking { assertEquals(5, sadMock()) } // throws MockKException
}

I have similar code above utilizing an interface that I mockked... I am curious what is the difference with yours that it is passing but mine is not...?

@andrebarrett
Copy link

andrebarrett commented Aug 29, 2022

I came across this issue myself today. I used this as a workaround:

...

val mockSubMessage = mockk<SubMessage<String>>()

val slot = slot<suspend (SubMessage<String>) -> Unit>()

every { mockConsumer.handler(capture(slot)) } returns Unit

consumerProcess.start()

slot.captured(mockSubMessage)

...

Instead of using the framework to coVerify or in my particular case coAnswer you can capture the suspend function and call it yourself.

In instances where an actual suspend function is passed through dependency injection for example, you already have the ability to pass a real function rather than a mock. This can be any function that does the verify for you.

This particular workaround may not suitable in all the cases mentioned already. It could help someone who is doing something similar to myself, however, so I thought I would share.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests