How to define testable timer loop in kotlin (android)?

Viewed 458

I want to have a periodic timer loop (e.g. 1 second intervals). There are many ways to do that, but I haven't found a solution that would be suitable for unit testing.

  • Timer should be precise
  • Unit test should be able to skip the waiting

The closest that I came to a solution was to use coroutines: A simple loop with delay, runBlockingTest and advanceTimeBy.

coScope.launch {
    while (isActive) {
        // do stuff
        delay(1000L)
    }
}

and

@Test
fun timer_test() = coScope.runBlockingTest {
    ... // start job
    advanceTimeBy(9_000L)
    ... // cancel job
}

It works to some degree, but the timer is not precise as it does not account for the execution time.

I haven't found a way to query internal timer used in a coroutine scope or a remaining timeout value inside withTimeoutOrNull:

coScope.launch {
    withTimeoutOrNull(999_000_000L) {  // max allowed looping time
        while (isActive) {
            // do stuff
            val timeoutLeft // How to get that value ???
            delay(timeoutLeft.mod(1000L))
        }
    }
}

Next idea was to use ticker:

coScope.launch {
    val tickerChannel = ticker(1000L, 0L, coroutineContext)
    var referenceTimer = 0L
    for (event in tickerChannel) {
         // do stuff
         println(referenceTimer)
         referenceTimer += 1000L
    }
}

However, the connection between TestCoroutineDispatcher() and ticker does not produce right results:

private val coDispatcher = TestCoroutineDispatcher()

@Test timerTest() = runBlockingTest(coDispatcher) {
    myTimer.lauchPeriodicJob()
    advanceTimeBy(20_000L)  // or delay(20_000L)
    myTimer.cancelPeriodicJob()
    println("end_of_test")
}

rather consistently results in:

0
1000
2000
3000
4000
5000
6000
end_of_test

I am also open for any alternative approaches that satisfy the two points above.

0 Answers
Related