Search code examples
androidjunit4kotlin-stateflow

Unit Testing StateFlow Created By StateIn


I have a viewmodel class that uses a StateFlow that is created using the StateIn operator.

I.E.

private val _state = MutableStateFlow(MyState())
private val myItems = myRepository.myFlow.stateIn(
        viewModelScope, SharingStarted.WhileSubscribed(), emptyList<MyObject>())
val state: StateFlow<MyState> = combine(_state, myItems ) { state, myItems ->
        ..///
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), MyState())

When reading the documentation and various articles and SO questions (which are outdated), I saw that you are supposed to test this by using:

backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
        //Collect your flow
    }

The problem I am having is that when I create a unit test to test my viewmodel, the collection of the state is not fast enough for the assertions that take place.

@Test
fun checkMyItemTest() = runTest {

        val results = mutableListOf<MyState>()
        val job = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
            viewModel.state.collect {
                results.add(it)
            }
        }

        viewModel.onEvent(MyEvent.DoSomething())

        assert(results[0].myStateParameter) //Perform some check on results[0]
        assert(results[1].myStateParameter) //Perform some check on results[1]

        job.cancel()
    }

The test above fails in the second assertion, since results has only one element in it. I have gotten it to work by shoving a Thread.sleep call of 50ms between the two assert calls, but that does not seem like the best approach to me.

@Test
    fun checkMyItemTest() = runTest {
    
            val results = mutableListOf<MyState>()
            val job = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
                viewModel.state.collect {
                    results.add(it)
                }
            }
    
            viewModel.onEvent(MyEvent.DoSomething())
    
            assert(results[0].myStateParameter) //Perform some check on results[0]
            Thread.sleep(50) /// <---- THIS
            assert(results[1].myStateParameter) //Perform some check on results[1]

            job.cancel()
        }

I am not interested in using Turbine at the moment

So I am looking to understand how I can test my flow properly and how I can collect the values emitted by it.


Solution

  • I am also a newbie to testing but from what I have read runTest{} skips delay and is not much diffrent from runBlocking so this might work:

    @Test
    fun checkMyItemTest() = runTest {
    
        val results = Channel<MyState>() // this is a rendevous channel
        val job = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
            viewModel.state.collect {
                results.send(it)
            }
        }
    
        viewModel.onEvent(MyEvent.DoSomething())
    
        val result1 = channel.receive() //suspends till it has a corresponding send call
        val result2 = channel.receive()
    
        assert(result1.myStateParameter) //Perform some check on result1
        assert(result2.myStateParameter) //Perform some check on result2
    }
    

    I have not run the code.