Unit Testing of ViewModel with Kotlin Coroutines and LiveData in Android

20 Dec, 2021
The official documentation says that coroutines are lightweight threads. By lightweight, it means that creating coroutines doesn’t allocate new threads. Instead, they use predefined thread pools and smart scheduling for the purpose of which task to execute next and which tasks later. In this article, we will learn how to write a unit test for a ViewModel using Kotlin Coroutines and LiveData and adhering to a basic MVVM Architecture. We are writing a unit test to check the above data. To keep things simple, the project employs a basic MVVM architecture. The complete code for unit testing mentioned in the blog can be found in the project itself.
This SingleNetworkCallViewModel is essentially a ViewModel that is associated with a SingleNetworkCallActivity, which causes the ViewModel to fetch a list of users to render into the UI. The SingleNetworkCallViewModel then uses the ApiHelper to query the data layer for a list of users. The ViewModel, as shown below, makes use of Kotlin Coroutines and LiveData.
Kotlin
class SingleNetworkCallViewModel(    private val gfgApi: GfgApi,    private val gfgDB: DatabaseHelper) : ViewModel() {    private val gfgUsers = MutableLiveData<Resource<List<ApiUser>>>()    init {        fetchGfgUsers()    }    private fun fetchGfgUsers() {        gfgVM.launch {            gfgUsers.postValue(Resource.loading(null))            try {                val gfgUsersFromApi = gfgApi.getGfgUsers()                gfgUsers.postValue(Resource.success(gfgUsersFromApi))            } catch (e: Exception) {                gfgUsers.postValue(Resource.error(e.toString(), null))            }        }    }    fun getGfgUsers(): LiveData<Resource<List<ApiUser>>> {        return gfgUsers    }}
Now we must write a unit test for this ViewModel, which makes use of Kotlin Coroutines and LiveData. First, we must configure the test’s dependencies, as shown below:
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:3.3.2"
testImplementation 'androidx.arch.core:core-testing:2.1.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2'
Make sure you’re using the most recent version available at the time you’re reading this article. This is significant because each release includes numerous bug fixes. Let’s move on to the test package, where we’ll write the ViewModel unit test. Now we must create the TestRule, which we will call TestCoroutineRule, and place it in the utils package.
Kotlin
@ExperimentalCoroutinesApiclass GfgCourtine : TestRule {    private val gfgCourtineDispatcher = GfgCourtineDispatcher()    private val gfgCourtineDispatcherScope = GfgCourtineDispatcherScope(gfgCourtineDispatcher)    override fun apply(base: Statement, description: Description?) = object : Statement() {        @Throws(Throwable::class)        override fun evaluate() {            Dispatchers.setMain(gfgCourtineDispatcher)            base.evaluate()            Dispatchers.resetMain()            gfgCourtineDispatcherScope.cleanupTestCoroutines()        }    }    fun runBlockingTest(block: suspend GfgCourtineDispatcherScope.() -> Unit) =        gfgCourtineDispatcherScope.runBlockingTest { block() }}
It allows the main dispatcher to use TestCoroutineDispatcher during the unit test. It resets and cleans up after the test. Now, we’ll add SingleNetworkCallViewModelTest to the appropriate location within the test package, as shown below:
Kotlin
@ExperimentalCoroutinesApi@RunWith(MockitoJUnitRunner::class)class GfgSingle {        @get:Rule    val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()    @get:Rule    val gfgTest = TestCoroutineRule()    @Mock    private lateinit var gfgApi: GfgApi    @Mock    private lateinit var gfgDBHelper: GfgDBHelper    @Mock    private lateinit var apiUsersObserver: Observer<Resource<List<ApiUser>>>    @Before    fun doSomeSetup() {        // do something if required    }          @Test    fun givenServerResponse200_whenFetch_shouldReturnSuccess() {        testCoroutineRule.runBlockingTest {            doReturn(emptyList<ApiUser>())                .`when`(gfgApi)                .getGfgUser()            val viewModel = SingleNetworkCallViewModel(gfgApi, gfgDBHelper)            viewModel.getGfgUser().observeForever(apiUsersObserver)            verify(gfgApi).getGfgUser()            verify(apiUsersObserver).onChanged(Resource.success(emptyList()))            viewModel.getGfgUser().removeObserver(apiUsersObserver)        }    }          @Test    fun givenServerResponseError_whenFetch_shouldReturnError() {        testCoroutineRule.runBlockingTest {            val someGeekyError = "Something is not right"            doThrow(RuntimeException(someGeekyError))                .`when`(gfgApi)                .getGfgUser()            val viewModel = SingleNetworkCallViewModel(gfgApi, gfgDBHelper)            viewModel.getGfgUser().observeForever(apiUsersObserver)            verify(gfgApi).getGfgUser()            verify(apiUsersObserver).onChanged(                Resource.error(                    RuntimeException(someGeekyError).toString(),                    null                )            )            viewModel.getGfgUser().removeObserver(apiUsersObserver)        }    }          @After    fun tearDown() {        // do something if required    }      }
In this case, we used the InstantTaskExecutorRule, which is required when testing code with LiveData. If we do not use this, we will get a RuntimeException in Android related to Looper. We mocked ApiHelper, DatabaseHelper, and other components and wrote two tests:
When the server returns 200, the UI layer should receive success.When the server returns an error, the UI layer should receive an error as well.
When the server returns 200, the UI layer should receive success.
When the server returns an error, the UI layer should receive an error as well.
In the first, we mocked the ApiHelper, causing it to return success with an empty list. Then we fetch and validate. Similarly, in the second one, we mocked the ApiHelper in order to return an error. Then we fetch and validate. We have successfully mocked the data and we are now ready to go away with all these methods!
Picked
Android
Kotlin
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
How to Add Views Dynamically and Store Data in Arraylist in Android?
Android SDK and it's Components
Flutter - Custom Bottom Navigation Bar
How to Communicate Between Fragments in Android?
Retrofit with Kotlin Coroutine in Android
How to Add Views Dynamically and Store Data in Arraylist in Android?
Android UI Layouts
Kotlin Array
How to Communicate Between Fragments in Android?
Retrofit with Kotlin Coroutine in Android