When unit testing a viewmodel with a repository that returns a flow, an error happen when converting it to a livedata

3.8k views Asked by At

I need some help about writing unit tests in android, related to the viewmodel, livedata and flow mechanics and dispatching.

First of all, im writing unit tests, and not instrumeted test.

Actually, im creating an Unit test for an android app, for testing a ViewModel that uses a repository for fetching some data from internet.

The code for the viewmodel im using is like this:

class ViewModel(private var repository: Repository? = Repository()) :
  androidx.lifecycle.ViewModel() {

  val data: LiveData<Result<Item>> = repository!!.remoteData.asLiveData()
}

The unit test code is as follows:

import junit.framework.TestCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.junit.MockitoJUnitRunner

@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class ViewModelTest : TestCase() {
    private val testDispatcher = TestCoroutineDispatcher()
    private lateinit var repository: Repository
    private lateinit var viewModel: ViewModel

    @Before
    public override fun setUp() {
        Dispatchers.setMain(testDispatcher)
        repository = mock(Repository::class.java)
        viewModel = ViewModel(repository)
    }

    @After
    public override fun tearDown() {
        super.tearDown()
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `remote data is returned`() = runBlockingTest {
        try {
          `when`(repository.remoteData).thenReturn(
            flowOf(Result.success(Item(SampleData.remoteData.apiResult!!)))
          )
          viewModel.data.observeForever { result ->
                assertTrue(result.isSuccess)
            }
        } catch (exception: Exception) {
            fail()
        }
    }

}

When creating the unit test, and running it, the following error happen:

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method androidx.lifecycle.FlowLiveDataConversions.asLiveData, parameter $this$asLiveData

As for the error, it seems that i need to pass a parameter to the viewmodel.data value, but, which one? as per the code, it not need parameters.

I like to know about mocking the methods that returns a flow object, as the asLiveData() function is the one that, when running the test, throws the exception above.

Also, i think i need to know about the observeForever function for executing and observing values from the livedata, after all, is then observing where i can assert the results of the unit test.

Any help would be great. :)

Im using the following libraries in the app build.gradle file:

    testImplementation "junit:junit:4.13"
    testImplementation "com.squareup.okhttp3:mockwebserver:4.7.2"
    testImplementation "org.mockito:mockito-core:3.3.3"
    testImplementation "androidx.arch.core:core-testing:2.1.0"
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2"
    androidTestImplementation "androidx.test.ext:junit:1.1.2"
    androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
1

There are 1 answers

1
Crul On

You need to mock the repository.remoteData first, and after that you can initialize the ViewModel

`when`(repository.remoteData).thenReturn(
            flowOf(Result.success(Item(SampleData.remoteData.apiResult!!)))
          )
viewModel = ViewModel(repository)