Kotlin flow test not emitting all value

1.3k views Asked by At

I am very new in Unit testing , i am trying to test my flow using Turbine library, it is not emitting all value, here is my test

fun `send function should emit Loading and Content states`() = runTest {
    // Arrange
    val userProfile = UserProfile(login = "test_login")
    val contentState = UiState.Content(userProfile)


    coEvery {
        fakeRepository.getDetail(any())
    } returns userProfile


    // Act
    viewModel.send(userProfile.login!!)


    // Assert
    testScheduler.advanceUntilIdle()
    viewModel.uiState.test {
        assertEquals(UiState.Loading, awaitItem())
        assertEquals(contentState, awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}

and ViewModel is here, keep failing with reason

Expected UiState$Loading Actual :Content

    viewModelScope.launch(dispatchers.main) {
        flow {
            emit(UiState.Loading)
            val mResponse = userRepository.getDetail(login = login)
            emit(UiState.Content(mResponse))
        }.catch {
            UiState.Error(it.message.toString())
            it.printStackTrace()
        }.flowOn(dispatchers.main).collect {
            _uiState.value = it
        }
    }
2

There are 2 answers

0
Jitu On BEST ANSWER

Finally I got solution of my problem, I changed my dispatcher from UnConfinedTestDispatcher to StandardTestDispatcher, it starts working

standardtestdispatcher and unconfinedtestdispatcher

Here is my test code:

@OptIn(ExperimentalCoroutinesApi::class)
class TestDispatchers : DispatcherProvider {
private val testDispatcher = StandardTestDispatcher()
override val main: CoroutineDispatcher
    get() = testDispatcher
override val io: CoroutineDispatcher
    get() = testDispatcher
override val default: CoroutineDispatcher
    get() = testDispatcher
}

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(MockitoJUnitRunner::class)
class UserProfileViewModelTest {

// private var fakeRepository = FakeRepository()

private val testDispatchers = TestDispatchers()
private val fakeRepository = mockk<UserRepository>()
private val fakeDB = mockk<IRoomdatabase>()
private val viewModel =
    UserProfileViewModel(
        dispatchers = testDispatchers,
        userRepository = fakeRepository,
        userDatabase = fakeDB
    )
//private val coroutineScope = TestScope(testDispatchers.main)

@Before
fun setUP() {
      Dispatchers.setMain(testDispatchers.main)
}

@Test
fun `send function should emit Loading and Content states`() = runTest {
    // Arrange
    val login = "test_login"
    val userProfile = UserProfile(login = login)
    val contentState = UiState.Content(userProfile)


    coEvery {
        fakeRepository.getDetail(login)
    } returns userProfile


    viewModel.send(userProfile.login!!)
    // Assert

    assertTrue(viewModel.uiState.value == UiState.Empty)
    viewModel.uiState.test {
        //  coroutineScope.testScheduler.advanceUntilIdle()
        assertEquals(UiState.Empty, awaitItem())
        assertEquals(UiState.Loading, awaitItem())
        assertEquals(contentState, awaitItem())
        //cancelAndIgnoreRemainingEvents()
    }

    coVerify { fakeRepository.getDetail(login) }
}
0
Skizo-ozᴉʞS ツ On

I don't know how you initialise the viewModel but if you pass a coroutineScope as a parameter your test should be like :

@Test
internal fun test1(): Unit = coroutineScope.runTest {
    // Arrange
    val userProfile = UserProfile(login = "test_login")
    val contentState = UiState.Content(userProfile)


    coEvery {
        fakeRepository.getDetail(any())
    } returns userProfile


    viewModel.uiState.test {
        viewModel.send(userProfile.login!!)
        assertEquals(UiState.Loading, awaitItem())
        assertEquals(contentState, awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}

This is a general idea how would look like your test, I'm not sure you need the testScheduler.advanceUntilIdle() your coroutineScope should be private val coroutineScope = TestScope(UnconfinedTestDispatcher())