Skip to content

Testing 1.6.0 - Correct way to collect and verify StateFlow<T> emissions #3336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
joaooliveirazup opened this issue Jun 21, 2022 · 4 comments

Comments

@joaooliveirazup
Copy link

joaooliveirazup commented Jun 21, 2022

I’m trying to test my ViewModel State, with StateFlow and Mockk.
`private val _state = MutableStateFlow(HomeState())
val state = _state.asStateFlow()

fun sendEvent(homeEvents: HomeEvents) = when (homeEvents) {
    HomeEvents.FetchProducts -> fetchAvailableProducts()
}

private fun fetchAvailableProducts() {
    viewModelScope.launch(coroutineDispatcher) {
        _state.update { it.copy(isLoading = true) }
        when (val result = verifyProductsAvailableUseCase.invoke()) {
            is Result.Success -> {
                _state.update {
                    it.copy(products =
                    result.data.sortedBy { product -> product.match.not() })
                }
            }
            is Result.Error -> {
            }
        }
    }
}`

But in my test, the job is collecting only the initial value and the last value, ignoring the second update with param loading true.
What can be wrong on this test ?

@Test fun given call init should update state correctly`() =
runTest {
val mockProducts = BaseUnitTestSupportData.mockCardProductsPreApproved

        val testStateCollector = mutableListOf<HomeViewModelV2.HomeState>()

        coEvery { verifyProductsAvailableUseCase.invoke() } returns mockProducts.toSuccess()

        val job = launch(UnconfinedTestDispatcher(testScheduler)) {
            homeViewModel.state.toList(testStateCollector)
        }
       homeViewModel.sendEvent(HomeViewModelV2.HomeEvents.FetchProducts)
        job.cancel()

        testStateCollector.verifyOrder {
            verify(HomeViewModelV2.HomeState(false, emptyList()))
            verify(HomeViewModelV2.HomeState(true, emptyList()))
            verify(HomeViewModelV2.HomeState(false, mockProducts))
        }
    }`
@dkhalanskyjb
Copy link
Collaborator

Please use Kotlin Slack or StackOverflow for general questions.

Your question is difficult to read because of the problems with formatting, and the full test code (including the initialization code) is not provided, so I can't say anything for sure, but I'd guess that the issue is with the fact that fetchAvailableProducts launches a coroutine, the coroutine gets scheduled for execution, but does not execute until later. I'd try adding advanceUntilIdle after sendEvent.

Alternatively, maybe using the Turbine library to test the flow would help.

@joaooliveirazup
Copy link
Author

I did moved this line to out of scope the coroutines, then did worked..
I don’t know the reason.
_state.update { it.copy(isLoading = true) }

@hosseinaminii
Copy link

I have the same issue, but I can't move the _state.update out of the coroutine scope, How can I fix it?

@dkhalanskyjb
Copy link
Collaborator

The master issue for this topic is now #3939

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants