tseisel
01/01/2022, 9:04 PMclass SomeManager(
private val source: DataSource,
private val externalScope: CoroutineScope
) {
private val cache: SharedFlow<Foo> by lazy {
source.fooFlow.shareIn(externalScope, SharingStarted.EAGERLY, 1)
}
suspend fun readCached(): Foo {
return cache.first()
}
}
Why does the following test hangs forever? How can I test this kind of class that requires an external CoroutineScope ?
@Test fun test() = runTest {
val subject = SomeManager(someSource, this)
val cachedValue = subject.readCached()
assertEquals(expectedFoo, cachedValue)
// HANGS HERE - due to having at least one child coroutine?
}
Joakim Forslund
01/01/2022, 10:01 PMJoakim Forslund
01/01/2022, 10:01 PMJoffrey
01/02/2022, 7:19 AMCoroutineScope(this)
) and pass that one to your manager class, so that you can cancel it at the end of the test.
Currently you're correctly passing the test scope, but the shared flow's coroutine is never cancelled, so it's still running at the end of the test and counts as a leaktseisel
01/02/2022, 4:17 PMfun runWithinScope(testBody: suspend TestScope.(childScope: CoroutineScope) -> Unit) = runTest {
launch {
testBody(this)
coroutineContext.cancelChildren()
}
}
// Usage
runWithinScope { childScope ->
val testSubject = SomeManager(childScope)
// childScope cancels its children after test block
}
It works, but feels a bit hacky...Joffrey
01/02/2022, 10:53 PM@Test
fun test() = runTest {
val job = launch { // this: CoroutineScope - is a child scope
val subject = SomeManager(someSource, this)
val cachedValue = subject.readCached()
assertEquals(expectedFoo, cachedValue)
}
job.cancelAndJoin()
}
But what I meant initially was just:
@Test
fun test() = runTest {
val managerScope = CoroutineScope(currentCoroutineContext())
val subject = SomeManager(someSource, this)
val cachedValue = subject.readCached()
assertEquals(expectedFoo, cachedValue)
managerScope.cancel()
}