Monday, 10 June 2019

jUnit test with LiveData doesn't execute map operator in RxJava

Background:

I have a simple application that fetches movie list using rests API call. The project structure is given below,

Activity -> ViewModel -> Repository -> ApiService (Retrofit Interface)

  1. The activity subscribes to a LiveData and listens for events changes

  2. The ViewModel hosts the MediatorLiveData observed by the activity. Initially the ViewModel sets a Resource.loading(..) value in MediatorLiveData.

  3. The ViewModel then calls the repository to get the movie list from ApiService

  4. The ApiService returns a LiveData of either Resource.success(..) or Resource.error(..)

  5. The ViewModel then merges LiveData result from ApiService in MediatorLiveData

My Queries:

  1. Inside the unit test, only the first emit Resource.loading(..) is made by MediatorLiveData from ViewModel. The MediatorLiveData never emits any data from the repository.
  2. Is this the right way to handle loading, success and error events with a single MediatorLiveData?

ViewModel.class

private var discoverMovieLiveData: MediatorLiveData<Resource<DiscoverMovieResponse>> = MediatorLiveData()

fun observeDiscoverMovie(): LiveData<Resource<DiscoverMovieResponse>> {
        return discoverMovieLiveData
    }

fun fetchDiscoverMovies(page: Int) {

        discoverMovieLiveData.value = Resource.loading(null) // this emit get observed immediately 

        val source = movieRepository.fetchDiscoverMovies(page)
        discoverMovieLiveData.addSource(source) {
            discoverMovieLiveData.value = it // never gets called
            discoverMovieLiveData.removeSource(source)
        }
    } 

Repository.class

fun fetchDiscoverMovies(page: Int): LiveData<Resource<DiscoverMovieResponse>> {
        return LiveDataReactiveStreams.fromPublisher(
            apiService.fetchDiscoverMovies(page)
                .subscribeOn(Schedulers.io())
                .map { d ->
                    Resource.success(d) // never gets called in unit test
                }
                .onErrorReturn { e ->
                    Resource.error(ApiErrorHandler.getErrorByThrowable(e), null) // // never gets called in unit test
                }
        )
    }

Unit Test

@Test
fun loadMovieListFromNetwork() {
        val mockResponse = DiscoverMovieResponse(1, emptyList(), 100, 10)
        val call: Flowable<DiscoverMovieResponse> = successCall(mockResponse) // wraps the retrofit result inside a Flowable<DiscoverMovieResponse>
        whenever(apiService.fetchDiscoverMovies(1)).thenReturn(call)

        viewModel.fetchDiscoverMovies(1)

        verify(apiService).fetchDiscoverMovies(1)
        verifyNoMoreInteractions(apiService)

        val liveData = viewModel.observeDiscoverMovie()
        val observer: Observer<Resource<DiscoverMovieResponse>> = mock()
        liveData.observeForever(observer)

        verify(observer).onChanged(
            Resource.loading(null) // never get other events e.g. Resource.success(..)
        )
    }

Resource is a generic wrapper class that wraps data for different scenario e.g. loading, success, error.

class Resource<out T>(val status: Status, val data: T?, val message: String?) {
.......
}



from jUnit test with LiveData doesn't execute map operator in RxJava

No comments:

Post a Comment