mockk: Odd failure message with SpyK and Coroutine

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

Also posted to: https://stackoverflow.com/questions/65492003/strange-failure-message-extra-params-when-verifying-with-spyk-and-coverify

Expected Behavior

Message on failing to verify spy matches actual function call

Current Behavior

What is the current behavior?

I have an integration test for an Android App where I am passing a spied Retrofit service (coroutine-based) to my repository:

val apiSpy = spyk(PvApiService.getInstance())
val expectedTokenLength = 1290 // by definition

test("Token can be refreshed") {
    val repo = Repository(apiSpy)
    repo.reset()
    repo.refreshToken() // Suspends, run on IO thread
    coVerify (exactly = 1){apiSpy.tokenRetrofitService.getApiToken(any(), any()) }
    repo.tokenAvailable shouldBe true
    repo.token.length shouldBe expectedTokenLength
}

This fails verification on the spy with the following message (note that the other tests pass, which means that the call was actually made!):

Verification failed: call 2 of 2: PvApiTokenService(child of #2#3).getApiToken(any(), any(), any())) was not called
java.lang.AssertionError: Verification failed: call 2 of 2: PvApiTokenService(child of #2#3).getApiToken(any(), any(), any())) was not called

My corresponding unit test for the repository, using a mock, rather than a spy, behaves as expected:

val mockApi = mockk<PvApiService>(relaxed = true)
val testToken = "a token"

test("Token can be refreshed") {
    coEvery { mockApi.tokenRetrofitService.getApiToken(any(), any()) } returns testToken
    val repo =  Repository(mockApi, ProjectConfig.testDispatcherProvider)
    repo.refreshToken()
    coVerify (exactly = 1){ mockApi.tokenRetrofitService.getApiToken(any(), any()) }
    repo.token shouldBe testToken
    repo.tokenAvailable shouldBe true
}

I do not understand the failure message when using the spy. I am verifying getApiToken(any(), any()) (i.e. any() two times), while the failure message refers to getApiToken(any(), any(), any())) (i.e. any() three times).

What have I done, that is making MockK try to verify the call on the spy with an additional parameter please?

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.10.4
  • OS: Windows
  • Kotlin version: 1.4.20
  • JDK version: 1.8
  • JUnit version: 1.12
  • Type of test: unit test/integration test

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 8
  • Comments: 25 (4 by maintainers)

Most upvoted comments

I assume this bug is still relevant? I don’t think it should be marked as stale

Suffering with this issue now. Hope it gets fixed soon. 🤞🏻

My setup is so simple and it still fails. =/

class TestUseCase() {
    suspend fun invoke() = "Test String"
}

class TestViewModel(usecase: TestUseCase): ViewModel() {

init {
        viewModelScope.launch {
            usecase.invoke()
        }
    }

}

Then in my tests:

@Test
    fun testUseCaseCall() = runTest {
        coEvery { testUseCaseMock.invoke() } returns "Test Mocked String"

        viewModel = TestViewModel(testUseCaseMock)

        coVerify(exactly = 1) { testUseCaseMock.invoke() }
    }

In my case I experienced this issue using mockk (without spyK) and changing to a UnconfinedTestDispatcher solved the issue.

I am also interested in this. I have a relaxed mocked variable of an interface, and if I explicitly make any kind of mock for the class then the coVerify will fail. If I don’t make a mock for it, then I guess the verify works.

For others seeing this, in my particular case the issue was that my coroutine was throwing an exception which I wasn’t seeing in the test, but it resulted in the mocked method not being called.

Any news on this issue? Still facing it on v1.13.5

I’m also getting similar error when I throw an exception from a flow and try to verify calls.

I tried @LukeMannering’s suggestion, but it didn’t work until I wrapped my suspending function with launch. So I’m just posting this here in case you tried the same but it didn’t work.

This failed ❌ :

    @Test(expected = Exception::class)
    fun `GIVEN api response is failure WHEN flow is collected THEN error is thrown`() = runTest {
        val getProfileUseCaseImpl = GetProfileUseCase(
            testScheduler,
            mockClientApi,
            mockProfileMapper
        )

        coEvery { mockClientApi.getProfile(any()) } throws Exception()

        getProfileUseCaseImpl().single()
        // this test will keep failing even after adding advanceUntilIdle() here

        coVerify(exactly = ONCE) { mockClientApi.getProfile(any()) }
        verify(exactly = NEVER) { mockProfileMapper.mapFromResponse(any()) }
    }
Not verified calls:
1) ClientApi(#1).getProfile(, continuation {})

but this passes ✅ :

    @Test(expected = Exception::class)
    fun `GIVEN api response is failure WHEN flow is collected THEN error is thrown`() = runTest {
        val getProfileUseCaseImpl = GetProfileUseCase(
            testScheduler,
            mockClientApi,
            mockProfileMapper
        )

        coEvery { mockClientApi.getProfile(any()) } throws Exception()

        launch {
            getProfileUseCaseImpl().single()
        }

        advanceUntilIdle()

        coVerify(exactly = ONCE) { mockClientApi.getProfile(any()) }
        verify(exactly = NEVER) { mockProfileMapper.mapFromResponse(any()) }
    }

In case it helps anyone else, adding this before calls to coVerify resolved this issue for me:

advanceUntilIdle()

I had a very similar setup to @heitor-zanetti above ☝️, using runTest from kotlinx.coroutines.test

As there’s been no change in this issue’s status and we are approaching 2 years since it was reported, I have added details of it to the MockK documentation, in the Spy and Coroutines sections.