I'd like to test the following class with the new ...
# coroutines
t
I'd like to test the following class with the new `runTest`utility:
Copy code
class 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 ?
Copy code
@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?
}
j
I think you need to make use of the TestDispatcher
Look into creating a Test rule that setups that up
j
You should create a child scope (e.g with
CoroutineScope(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 leak
t
I ended up writing the following:
Copy code
fun 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...
j
If you're launching a child coroutine anyway, why not simply cancel the `launch`ed job directly ?
Copy code
@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:
Copy code
@Test
fun test() = runTest {
    val managerScope = CoroutineScope(currentCoroutineContext())
    val subject = SomeManager(someSource, this)
    val cachedValue = subject.readCached()
    assertEquals(expectedFoo, cachedValue)
    managerScope.cancel()
}