question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

suspendCoroutine is not testtable

See original GitHub issue

Consider following scenario

Function to test:

fun execute(appConfig: AppConfig): Deferred<String> = async {
   registerDevice(appConfig.serviceId)    
}

private suspend fun registerDevice(serviceId: String): String = suspendCoroutine {
   //some nasty 3rd party library delivering data in a callback
   lib.registerDeviceForService(object: Callback {
          override fun onSuccess(response) {
             it.resume(response.data)
          }
      }
   )
}

Here is the test:

    private val callbackCaptor = argumentCaptor<Callback<Response>>()

    @Test
    fun `ok result of registration results in success`() = blocking {
        val deferred = classToTest.execute(appConfig)
        mockRegisterResponseSuccess()
        val result = deferred.await()
        result.should.be.equal(trackingId)
    }
 
    private fun mockRegisterResponseSuccess(result: ApiResponseStatus) {
        then(lib).should().registerDeviceForService(callbackCaptor.capture())
        callbackCaptor.firstValue.onSuccess(Response(data = "testdata"))
    }

suspendCoroutine blocks the call and code never gets to the mockRegisterReponseSuccess call to mock and capture the callback

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
itsymbalcommented, Feb 8, 2019

for posterity: using MockK you can write something like this val lib: Lib = mockk() val slot = slot<LibCallback>() every { lib.doStuff(capture(slot)) } answers { slot.captured.onResultReady(“result ready”) }

2reactions
qwwdfsadcommented, Aug 24, 2018
  @Test
    fun `executes and gets result`() = blocking {
        val result = classToTest.execute().await()
        then(lib).should().doStuff(libCallbackCaptor.capture())
        libCallbackCaptor.firstValue.onResultReady("result ready")
                result.should.be.equal("result ready")

    }

Why are you expecting this test to work? You call await (suspending the whole coroutine) and then when deferred is complete, you are mocking the service which is supposed to complete deferred. That’s exactly the case I’ve described above with CompletableFuture. You should first mock the service and only then call await on it.

About mocking Kotlin code – it’s not a kotlinx.coroutines issue, just use a Kotlin-mockito.

Task is to test that calling testClassInstance.execute() returns back “result ready”

Something like this modulo proper mocking

 @Test
    fun sampleTest() = runTest {
        val lib = mock(Lib::class.java)
        val testInstance = TestClass(lib)
        Mockito.`when`(lib.doStuff(ArgumentMatchers.any(LibCallback::class.java))).thenAnswer { invocation -> (invocation.getArgument(0) as LibCallback).onResultReady("result ready") }
        val deferred = testInstance.execute()
        assertEquals("result ready", deferred.await())
    }
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to wait for suspendCoroutine in unit test? - Stack Overflow
When using suspendCoroutine, the Dispatchers.Unconfined will not be suspended and later resumed, but instead will simply return.
Read more >
Best practices for coroutines in Android
This page presents several best practices that have a positive impact by making your app more scalable and testable when using coroutines.
Read more >
Playing with Kotlin in Android: coroutines and how to get rid of ...
In this post I will talk about how Kotlin coroutines allows you to get rid of the callback hell in your Android code...
Read more >
Unit Testing Coroutine Suspend Functions using ... - craigrussell
Specifically, the test will fail with the following reason: java.lang.IllegalStateException: This job has not completed yet at kotlinx.
Read more >
[Solved]-Callback and Co-routine on AWS SDK-kotlin
SignInResult = suspendCoroutine { continuation -> signIn(userName, password, someParam, ... I'm not sure if ResultResponse is your own class. In case it is, ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found