Skip to content
Related Articles

Related Articles

Improve Article
Save Article
Like Article

Unit Testing of ViewModel with Kotlin Coroutines and LiveData in Android

  • Last Updated : 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.

Step by Step Implementation

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




@ExperimentalCoroutinesApi
class 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() }
}

Why is the preceding TestRule used?

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:

  1. When the server returns 200, the UI layer should receive success.
  2. 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!


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!