Hi, I have faced a problem with hot flows while un...
# coroutines
а
Hi, I have faced a problem with hot flows while unit testing. I saw people had similar problems but not in the exact way, so it would be nice if someone could explain the following behaviour: 1. This test fails with
java.lang.IllegalStateException: This job has not completed yet
Copy code
@Test
fun testHotFlow() = coroutinesTestRule.testDispatcher.runBlockingTest {
    val eventChannel = Channel<String>()
    val events = eventChannel.receiveAsFlow()

    val event = events.first()
    assertEquals(event, null)
}
2. This test doesn’t fail
Copy code
@Test
fun testHotFlow() = coroutinesTestRule.testDispatcher.runBlockingTest {
    val eventChannel = Channel<String>()
    val events = eventChannel.receiveAsFlow()

    launch {
        eventChannel.send("First element")
    }

    val event = events.first()
    assertNotNull(event)
}
e
did you mean
.firstOrNull()
?
а
it’s the same with first() or firstOrNull()
e
your test fails either way, but
.first()
is wrong even if it worked, because it won't return null
whereas
.firstOrNull()
is only broken because it needs the flow to either emit or terminate (which is also wrong with
.first()
)
а
it needs the flow to either emit or terminate
But why does it fail with “java.lang.IllegalStateException: This job has not completed yet” in the first example. And why it doesn’t fail after it emits something (
eventChannel.send("First element")
)? Isn’t the job still running?
Or is it because of how first/firstOrNull works?
e
in the first case, the test dispatcher knows that there are zero runnable children: you cannot possibly proceed, so the test ends instead of deadlocking forever
in the second case, first/firstOrNull terminates as soon as it has an answer, so everything can wrap up
compare to if you used
.shareIn(this)
then the second test wouldn't pass because that launches a coroutine that will keep running
а
zero runnable children
Meaning that nothing can send/terminate the flow?
so everything can wrap up
but isn’t the flow still running? Or it is the
first()
function that creates the job that “has not completed yet”
a coroutine that will keep running
Copy code
@Test
fun testHotFlow() = coroutinesTestRule.testDispatcher.runBlockingTest  {
    val eventChannel = Channel<String>()
    val events = eventChannel.receiveAsFlow().shareIn(this, SharingStarted.Lazily)

    launch {
        eventChannel.send("First element")
    }

    val event = events.first()
    assertNotNull(event)
}
you are right, it fails, but with more meaningful message:
Copy code
Test finished with active jobs: ["coroutine#2":StandaloneCoroutine{Active}@244e30dd]
kotlinx.coroutines.test.UncompletedCoroutinesError: Test finished with active jobs: ["coroutine#2":StandaloneCoroutine{Active}@244e30dd]
e
1. nothing is able to proceed 2. the flow is not still running. receiveAsFlow does nothing when there are no collectors, and first stops collecting as soon as it has an answer
if you want to see .first() throw or .firstOrNull() return null, simply close the channel (or use a different kind of flow that ends as well)
а
the channel itself is a private field in my view model, and I collect events flow in my fragment. So I wanted to test that events flow is empty in one case and it’s has a particular value in another
e
if the channel never closes, you don't know that is empty
а
I can send the value to the channel by calling a function in my view model. But if I don’t call it, the channel is empty and it results in the exception
you don’t know that is empty
so it’s wrong to assume that it’s empty in the first place