Hey. I’m trying to understand the `runTest` behavi...
# coroutines
a
Hey. I’m trying to understand the
runTest
behavior that makes it run all the delays at the end of the test body. I think I saw it mentioned somewhere, maybe in some GitHub issue, but I can’t find any mentions in the documentation regarding that behavior. Example:
Copy code
private val testCoroutineScheduler = TestCoroutineScheduler()
private val standardTestDispatcher = StandardTestDispatcher(testCoroutineScheduler)

@Test
fun `should execute the coroutine with delays`() = runTest(standardTestDispatcher) {
    var iterations = 0

    val job = launch {
        while(isActive) {
            println("iteration")
            iterations++
            delay(10)
        }
    }

    iterations.shouldEqual(0) // this is an assertion

    runCurrent()
    iterations.shouldEqual(1)

    advanceTimeBy(5)
    iterations.shouldEqual(1)
    advanceTimeBy(5)
    iterations.shouldEqual(1)

    runCurrent()
    iterations.shouldEqual(2)

    advanceTimeBy(10)
    runCurrent()
    iterations.shouldEqual(3)

    job.cancel()
}
If I remove
job.cancel()
at the end, the test is never going to finish and I’m going to be flooded by the
println
invocations. That behavior is quite problematic for me sometimes and that’s why I’m trying to understand it better. There are some cases where I: • want to control the scheduler to see how many times something periodical happened • want the test to finish despite the lack of direct access for the launched coroutine containing the delay For example:
Copy code
private val testCoroutineScheduler = TestCoroutineScheduler()
private val standardTestDispatcher = StandardTestDispatcher(testCoroutineScheduler)

class Refresher(scope: CoroutineScope, refreshingDispatcher: CoroutineDispatcher) {

    var refreshCount = 0

    init {
        scope.launch(refreshingDispatcher) {
            while (isActive) {
                refreshCount++
                delay(10)
            }
        }
    }
}

@Test
fun `refreshing should work`() = runTest(standardTestDispatcher) {
    val refresher = Refresher(this, standardTestDispatcher)

    refresher.refreshCount.shouldEqual(0)
    runCurrent()
    refresher.refreshCount.shouldEqual(1)
    advanceTimeBy(15)
    refresher.refreshCount.shouldEqual(2)

    // this test never ends and I can't cancel the launched coroutine
}
I can make it work by removing
runTest
but I’m simply not sure if this is what I should do in this scenario.
Copy code
@Test
fun `refreshing should work`() {
    val refresher = Refresher(CoroutineScope(standardTestDispatcher), standardTestDispatcher)

    refresher.refreshCount.shouldEqual(0)
    testCoroutineScheduler.runCurrent()
    refresher.refreshCount.shouldEqual(1)
    testCoroutineScheduler.advanceTimeBy(15)
    refresher.refreshCount.shouldEqual(2)
}
Any advices?
j
use cancel children on the test scope? or create a child scope that you can cancel into which your infinite things are launched.
🙏 1
a
@jw Thanks for the suggestions. Indeed, calling `coroutineContext.cancelChildren()`at the end of the test solves my problem. By “child scope” did you mean creating a scope this way?
Copy code
runTest {
    val childScope = CoroutineScope(coroutineContext + Job(coroutineContext[Job]))

    // ... use childScope here ...
    childScope.cancel()
}
or something else?
And, by the way, do you recall any documentation mentioning that behaviour of
runTest
?