2

As we know the default dispatcher for viewModelScope executes in parallel. How do I make viewModelScope wait for the suspend function in unit test . In my code i would like to get the result from repository.getData() so that my assertion passes. Right now, i don't get the result on time.

class MainViewModel(private val repository: Repository,
                     private val dispatcher: CoroutineDispatcherProvider) : ViewModel() {

    private val viewState = MutableLiveData<Results<Data>>()
    fun getViewState() : LiveData<Results<Data>> = viewState



    fun getData(query: search) {
        viewState.value = Loading
        viewModelScope.launch(dispatcher.main()) {
            val results = repository.getData(query) //need to wait for this
            when(results){
                    is Success -> {
                        viewState.value = Success(results.data)
                    }
                    is Error -> viewState.value = Error(“Error”)
                }
        }

MainViewModelTest:

 Class MainViewModelTest {

    @get:Rule
    val instantExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    val coroutineTestRule = CoroutineTestRule()


    @Test
    fun `test get data`() = coroutineTestRule.testCoroutineDispatcher.runBlockingTest {
        coEvery{repository.getData(“query”)} returns Success(Data)

        var observer:Observer<Results<Data>> = mock()

        viewModel.getViewState().observeForever(observer)

        viewModel.getData(“query”)

          assertEquals(Loading, state)
          assertEquals(resultSuccess, state)

       }

    }
irobotxx
  • 5,963
  • 11
  • 62
  • 91
  • 1
    "As we know viewModelScope executes in parallel" -- what happens in parallel is determined by the dispatcher, not the `CoroutineScope`. The default dispatcher for `viewModelScope` is `Dispatchers.Main`, which has a single thread (Android's main application thread). – CommonsWare Mar 30 '20 at 20:04
  • Beyond that, I use [this `MainDispatcherRule`](https://gitlab.com/commonsguy/cw-andexplore/-/blob/v1.0/T36-Internet/ToDo/app/src/test/java/com/commonsware/todo/MainDispatcherRule.kt) to allow me to control when coroutines for `Dispatchers.Main` get executed. – CommonsWare Mar 30 '20 at 20:10
  • @CommonsWare Thanks for the response, Yes, sorry i didn't word it correctly. i have edited it. Should i also add the MainDispatcherRule class to my test along with CoroutineTestRule?. As the CoroutineTestRule is somehow similar. How would i use this MainDispatcherRule to solve the issue? Thanks – irobotxx Mar 31 '20 at 07:15
  • Sorry, I didn't completely understand what you were aiming for. I think your test is not created correctly. I often use https://github.com/jraska/livedata-testing and the `TestObserver` that it offers, which would replace your `observer` and allow you to assert the results of the observation when it occurs. IOW, you don't want to to block `getData()`; you want to block the assertion. – CommonsWare Mar 31 '20 at 11:26
  • I am observing a similar issue with `viewModelScope` functions launched in the ViewModel from a unit test. According to [Craig Russell's](https://twitter.com/trionkidnapper) [post here](https://craigrussell.io/2019/11/unit-testing-coroutine-suspend-functions-using-testcoroutinedispatcher/), the solution seems to be to inject the CoroutineScope into the ViewModel. – AdamHurwitz Jun 08 '20 at 20:59
  • Does this answer your question? [Coroutines - unit testing viewModelScope.launch methods](https://stackoverflow.com/questions/55765190/coroutines-unit-testing-viewmodelscope-launch-methods) – AdamHurwitz Jun 13 '20 at 01:17

1 Answers1

-1

I think you should use observer.onChanged() for assertions and verifications. I don't see a problem otherwise.

val successResult = Result.Success(Data)
coEvery{repository.getData(“query”)} returns successResult

verify { observer.onChanged(Result.Loading) }
verify { observer.onChanged(successResult) }
Jemshit
  • 9,501
  • 5
  • 69
  • 106