2

I have an android viewmodel class with the following property

private val _trainingNavigationEvents = MutableSharedFlow<NavigationEventTraining>(replay = 0)
    val trainingNavigationEvents = _trainingNavigationEvents.asSharedFlow()

fun navigate(navigationEvent: NavigationEventTraining) {
        viewModelScope.launch {
            _trainingNavigationEvents.emit(navigationEvent)
        }
    }

I am using a SharedFlow as it solves the SingleLiveEvent problem.

The issue arises when I try and unit test the code. I can't see how to use turbine (or supplied primitives) to get it to work.

    @ExperimentalTime
    @Test
    fun `navigate`() = runBlockingTest {
        viewModel.handleIntent(TrainingViewModel.TrainingIntent.ShowQuestions)

        viewModel.navigationEvents.test {
            assertEquals(
                TrainingViewModel.TrainingNavigationEvent.NavigateToQuestions::class,
                expectItem()::class
            )
            cancelAndConsumeRemainingEvents()
        }
    }

and I get

kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms

I know that a SharedFlow never completes and that may be part of the reason but I have been unable to find any examples of how to do this instead.

I am using Junit 5 and am using a TestCoroutineDispatcher class extension.

Rakesh Patel
  • 21
  • 1
  • 4

2 Answers2

6

In the Turbine's documentation there is a Hot Flows section, I think we could do something like this:

@ExperimentalTime
@Test
fun `navigate`() = runBlockingTest {
    viewModel.navigationEvents.test {
        viewModel.handleIntent(TrainingViewModel.TrainingIntent.ShowQuestions)
        assertEquals(
            TrainingViewModel.TrainingNavigationEvent.NavigateToQuestions::class,
            expectItem()::class
        )
        cancelAndConsumeRemainingEvents()
    }
}

pretty much moving the trigger inside of the Turbine's .test { block

epool
  • 6,710
  • 7
  • 38
  • 43
1

I posted a similar question, but my question is how to improve the Stateflow test.

I think the issue is that you need to call the ViewModel initialization in the test.

ex:

 @ExperimentalTime
    @Test
    fun `navigate`() = runBlockingTest {
        viewModel = ViewModel(...)
        viewModel.handleIntent(TrainingViewModel.TrainingIntent.ShowQuestions)

        viewModel.navigationEvents.test {
            assertEquals(
                TrainingViewModel.TrainingNavigationEvent.NavigateToQuestions::class,
                expectItem()::class
            )
            cancelAndConsumeRemainingEvents()
        }
    }
  • I found it useful to wrap the ViewModel under test in a @Before annotated function: using lateinit:``` private lateinit var viewModel: ViewModel @Before fun createViewModelUnderTest() { viewModel = ViewModel( ... ) } ``` – B_Nut Oct 12 '22 at 09:44