https://kotlinlang.org logo
#coroutines
Title
# coroutines
g

Guillermo Alcantara

02/02/2021, 12:58 AM
Anyone knows why a mutableSharedFlow.collect might not be called during a Unit Test?
Copy code
@Test
    fun testFoo() = runBlocking {
        val testCoroutineScope = TestCoroutineScope().apply {
            pauseDispatcher() // This needs to be here because the actual test, handles time.
        }
        val sharedFlow = MutableSharedFlow<Int>()
        val values = mutableListOf<Int>()
        println("before launch")
        val job = testCoroutineScope.launch {
            println("before collect")
            sharedFlow.collect {
                println("before adding $it")
                values.add(it)
            }
        }

        println("before emits")
        sharedFlow.emit(1)
        sharedFlow.emit(2)
        testCoroutineScope.runCurrent()

        assertEquals(mutableListOf(1, 2), values)

        job.cancel()
    }
The string "before adding XXX" is never called
f

FunkyMuse

02/02/2021, 8:00 AM
Because collect is blocking the execution
g

Guillermo Alcantara

02/02/2021, 4:11 PM
I don't understand what you mean, the collect is inside a launch. The string "before emits" does print.
t

tateisu

02/02/2021, 8:42 PM
Does SharedFlow store previously emitted values?
public fun <T> MutableSharedFlow(    replay: Int = 0, ...)
default value of replay is 0.
also, you might use job.join() before assertEquals
( i don't know what condition is used to stop collect ... )
g

Guillermo Alcantara

02/02/2021, 9:04 PM
Thanks Tate but having a replay value would change the business logic. (It also to my knowledge makes no difference.) I also tried having
extraBufferCapacity
, this doesn't seem to help either. I added logic to have a
job.join
and cancel the collect but again doesn't seem to help.
t

tateisu

02/02/2021, 9:26 PM
Do you need to emit first in in this test code ?
I don't know the correctness of the test that emits before collect for SharedFlow that doesn't have replayCache.
The order I think reasonable. Launch the coroutine to collect first. Then emit. The test code waits until the collect side finishes receiving the value.
g

Guillermo Alcantara

02/02/2021, 9:30 PM
I want to emit the values after the collect is set up. But even putting a big delay doesn't seem to make a difference.
t

tateisu

02/02/2021, 9:32 PM
I think I've answered at least your question well. I will not intervene in your design.
g

Guillermo Alcantara

02/02/2021, 9:35 PM
Sorry, I'm not following. Could you tell me how can I emit values after the collect is set up without a replay value?
Btw, the test passes if I comment
pauseDispatcher()
t

tateisu

02/02/2021, 9:50 PM
that case collect is start before emit
g

Guillermo Alcantara

02/02/2021, 9:54 PM
ahh, I think I see what's happening. I need to add a bunch of
testCoroutineScope.runCurrent()
g

Guillermo Alcantara

02/02/2021, 10:04 PM
For posterity this is how it's working for me:
Copy code
@Test
    fun testFoo() = runBlocking {
        val testCoroutineScope = TestCoroutineScope().apply {
            pauseDispatcher()
        }
        val sharedFlow = MutableSharedFlow<Int>(
            extraBufferCapacity = 2 // Without it, sharedFlow.emit won't have a space to save data. It will be collected
                                    // next time there's a testCoroutineScope.runCurrent()
        )
        val values = mutableListOf<Int>()
        println("before launch")
        val job = testCoroutineScope.launch {
            println("before collect")
            sharedFlow.collect {
                println("before adding $it")
                values.add(it)
            }
        }
        testCoroutineScope.runCurrent() // Allows the previous launch to start collecting

        println("before emits")
        sharedFlow.emit(1)
        sharedFlow.emit(2)
        testCoroutineScope.runCurrent()

        assertEquals(mutableListOf(1, 2), values)
        job.cancel()
    }
Thanks for the help!
2 Views