How to test Coroutines in Android App ?

Coroutines are used to handle long running and asynchronous code.

override suspend fun saveTask(task: Task) {
    coroutineScope {        launch { tasksRemoteDataSource.saveTask(task) }        launch { tasksLocalDataSource.saveTask(task) }    }}


@Testfun activeTaskDetails_DisplayedInUi() = runBlockingTest{
    // GIVEN - Add active (incomplete) task to the DB    val activeTask = Task("Active Task", "AndroidX Rocks", false)

    repository.saveTask(activeTask)
    // WHEN - Details fragment launched to display task    val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
    launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
    Thread.sleep(2000)

    // THEN - Task details are displayed on the screen    // make sure that the title/description are both shown and correct    onView(withId(task_detail_title_text)).check(matches(isDisplayed()))
    onView(withId(task_detail_title_text)).check(matches(withText("Active Task")))
    onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
    onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
    // and make sure the "active" checkbox is shown unchecked    onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
    onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))
}


One incorrect way to solve this problem of asynchronous thread test is using this:
 Thread.sleep(2000)

It's not correct becoz it's not tracking when these coroutines are actually finished or done.
It also doesn't ensure the order in which these coroutines will finish.
If we use thread too long, our test tends to be super slow.
So the best practice is to tell your test to wait and hope it waits long enough.

so instead in coroutines we use this : = runBlockingTest{
This = runBlockingTestwill ensure that the test.blocks untils all of the coroutines it starts are finished. It also run the code in coroutines immediately and in the order in which they are called.

The way how this = runBlockingTestwill do the job is by calling a TestCoroutineDispacher.
It uses this test dispacher to run and suspend functions in = runBlockingTest{

TestCoroutineDispatcher
They are the coroutine way of determining how a coroutine runs.
They control both things, like what thread the code should run on but also when and how the tasks
shoud run.
This dispatcher will run your code immediately and allows greater control over coroutine timing.
As long as your code and your test doesn't change what dispatcher that it's running on and it runs on a TestCoroutineDispatcher. The = runBlockingTestwill resolve the vast majority of your coroutine testing issues.

kotlinx-coroutines-test

Both = runBlockingTestand TestCoroutineDispatcher are part of kotlinx-coroutines-test a currently experimental library for testing coroutines. When you need to annotate the function or class with @ Experimental coroutines API to avoid seeing a warning.

Use  runBlockingTest{
Not
runBlocking :

  • Used outside of tests
  • Closer simulation to what the real code does 
  • Prefereable for fakes 

Finally, when you're writing code and test doubles, use runBlocking



# Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used

@ExperimentalCoroutinesApiclass TasksViewModelTest{

    val testDispacher : TestCoroutineDispatcher = TestCoroutineDispatcher()

    @Before    fun setupDispatcher(){
        Dispatchers.setMain(testDispacher)
    }

    @After    fun tearDownDispatcher(){
        Dispatchers.resetMain()
        testDispacher.cleanupTestCoroutines()
    }
    ....
// write you test here .. 

}




Comments

Popular Posts