1

I have this block:

try {
  withTimeout(200) {
    val response = datafetcher.fetchData(request)
  }
} catch (TimeoutCancellationException e) {
  returns response with some error message added
}

I am trying to write a unit test by simulating some time on the fetchData part instead of throwing a TimeoutException to cover the exception code.

I have tried doing this:

runTest {
  whenever(mockdatafetcher.fetchData(any())).thenAnswer(
    runBlocking {
      delay(7000)
      defaultResponse
    }
  )
}

But, in debug mode, I can see it reaches the fetchData part and waits 7 seconds but the TimeoutException is not triggered. It gets the defaultResponse and move on like no TimeoutException happened.

also tried adding a never ending while loop to somehow trigger the TimeoutCancellationException (guess only cancellable functions can be used)

Can anyone help me with what I am doing wrong here? Or how to correctly test this. Using mockito runTest for unit testing

3
  • Did you check stackoverflow question below? stackoverflow.com/questions/70658926/… Commented May 24 at 13:27
  • have seen this, couldn't comprehend much for my scenario of not getting a timedoutexception. Can you provide some more info in case am missing anything here Commented May 24 at 13:44
  • 1
    This could help to understand why runBlocking in runTest does not affect withTimeout stackoverflow.com/a/79622919/7613649 Commented May 24 at 17:17

2 Answers 2

0

I think that problem that runTest doesn't use the same korutine with the same time. I mean it just skip delay. Try to use runBlocking

If that's don't work, sorry, i don't know much about timeouts in kotlin, but maybe you need to use something like coAnswers: https://medium.com/@ralf.stuckert/testing-coroutines-timeout-36c22db1b06a https://notwoods.github.io/mockk-guidebook/docs/mocking/coroutines/

Sign up to request clarification or add additional context in comments.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
0

I've worked up a quick example where you can use Mockk for mocking the "fetch" request to ensure it never completes by using just awaits and then I've wrapped the call in question in an async block so that you can query the state of the coroutine at various points in time and show that after your timeout you get a TimeoutCancellationException:

import io.mockk.awaits
import io.mockk.coEvery
import io.mockk.just
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class SampleTest {
    private val fetcher = mockk<Fetcher>()

    @Test
    fun testTimeout() = runTest {
        val sample = Sample(fetcher = fetcher)

        // Ensure the fetching never completes
        coEvery { fetcher.fetch() } just awaits

        // Kick off the result using "async" to get a Deferred you can query
        val deferredResult = async { sample.getResult() }

        // Check that the result has not yet completed before waiting 200ms
        assertFalse(deferredResult.isCompleted)

        // Advance time by the timeout amount. This will *not* yet run any coroutines scheduled to
        // run at this time.
        testScheduler.advanceTimeBy(200)

        // Check that the result has not yet completed before running the scheduled tasks that
        // should happen after 200ms
        assertFalse(deferredResult.isCompleted)

        // Run the pending tasks and check that the result is complete
        testScheduler.runCurrent()
        assertTrue(deferredResult.isCompleted)

        // Check the result is as expected
        val result = deferredResult.await()
        assertTrue(result.isFailure)
        assertTrue(result.exceptionOrNull() is TimeoutCancellationException)
    }

}

interface Fetcher {
    suspend fun fetch(): Result<Unit>
}

class Sample(
    private val fetcher: Fetcher,
) {
    suspend fun getResult(): Result<Unit> =
        try {
            withTimeout(200) {
                fetcher.fetch()
                Result.success(Unit)
            }
        } catch (e: TimeoutCancellationException) {
            Result.failure(e)
        }
}

You can also simplify this example using runCatching instead of a try / catch :

class Sample(
    private val fetcher: Fetcher,
) {
    suspend fun getResult(): Result<Unit> =
        runCatching {
            withTimeout(200) {
                fetcher.fetch()
            }
        }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.