And another difference between standard and unconf...
# coroutines
m
And another difference between standard and unconfined test dispatchers which is difficult to understand for me:
Copy code
fun test() = runTest(UnconfinedTestDispatcher(), dispatchTimeoutMs = 200) {
            val scope = this
            val counter = MutableStateFlow(0)
            counter.test {
                awaitItem() shouldBe 0
                scope.launch {
                    counter.value = 1
                    counter.value = 0
                }
                awaitItem() shouldBe 1
                awaitItem() shouldBe 0
            }
        }
This code will run fine on
StandardTestDispatcher
but will time out on
UnconfinedTestDispatcher
. It's not flakiness, it will time out every single time, so I assume it's a difference between the two dispatchers.
Of course it's a distilled problem. In a real life scenario I was observing a state of a player which does something like this
Copy code
fun playSound(sound: Sound) {
    currentlyPlayingJob?.cancel()
    currentlyPlayingJob = playerScope.launch {
        _state.value = Playing(sound)
        realPlayer.playSound(sound.filePath)
        _state.value = Stopped
    }
}
and going from Stopped to Playing to Stopped again with
realPlayer.playSound
being mocked (
coJustRun { realPlayer.playSound(any()) }
) and 0-delay caused similar effect (works if you add even a 1ms delay between state changes)
e
StateFlow is allowed to conflate values, so your problem is that you are expecting something of the API that it doesn't provide
m
I do understand that part, I just can't understand the difference that the dispatcher makes here
r
This is mentioned in the documentation:
Please be aware that, like Dispatchers.Unconfined, this is a specific dispatcher with execution order guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing functionality, not the specific order of actions. See Dispatchers.Unconfined for a discussion of the execution order guarantees.
m
or should I rather say - which feature of
StandardTestDispatcher
causes it to register both values
OK, you're right @Robert Williams, I've even read this before but didn't really apply it to this problem. In my head it was rather about possible flakiness of the test. I expected the stateflow to conflate on e.g.
<http://Dispatchers.IO|Dispatchers.IO>
but it works fine. If I do`runBlocking(Dispatchers.Unconfined) {` it'll also hang forever. So the unconfined dispatcher causes a coroutine to never finish - I guess my question boils down to why this is the case.
r
It's just about execution order, you're relying on the second
awaitItem
to be woken up between the two
counter.value
calls
If it's woken up after the second value change the values will be conflated and so the third
awaitItem
will hang
m
hmmmm, but
awaitItem()
has a default mechanism that should only wait for one second and then cancel...
but you're right, that if I remove it, it will finish. Sooooo, unconfined breaks the timeout mechanism of turbine's awaitItem()
r
I've never heard of this 1 second timeout on turbine, are there docs for this?
To be fair, I've actually never tried `launch`ing from inside
test
m
r
I'd normally do it with fixed delays which is more verbose but feels more like a real use case
m
I was totally relying on the fact that this will kick in
r
Yeah, I know they cleaned up some of this because
runTest
now times out
(even though they actually function slightly differently)
m
all this confusion from something so simple... 🤯 anyway, thank you very much Robert 🙂
r
No problem, we also had a few annoying edge cases that were relying on turbine's specific timeout behaviour