Full source code is available at : https://github.com/AliRezaeiii/StarWarsSearch-RxPaging
I am trying to test my DetailViewModel. My expectation is Species and Films not be empty lists as I have for instance : when(service.getSpecie(anyString())).thenReturn(Single.just(specie))
. Here is my test :
class DetailViewModelTest {
@get:Rule
var rule: TestRule = InstantTaskExecutorRule()
@Mock
private lateinit var service: StarWarsService
private lateinit var specie: Specie
private lateinit var planet: Planet
private lateinit var film: Film
private lateinit var viewModel: DetailViewModel
@Before
fun setUp() {
initMocks(this)
// Make the sure that all schedulers are immediate.
val schedulerProvider = ImmediateSchedulerProvider()
val detailRepository = DetailRepository(service)
val character = Character(
"Ali", "127", "1385", emptyList(), emptyList()
)
viewModel = DetailViewModel(
schedulerProvider, character, GetSpecieUseCase(detailRepository),
GetPlanetUseCase(detailRepository), GetFilmUseCase(detailRepository)
)
specie = Specie("Ali", "Persian", "Iran")
planet = Planet("")
film = Film("")
}
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
`when`(service.getSpecie(anyString())).thenReturn(Single.just(specie))
`when`(service.getPlanet(anyString())).thenReturn(Single.just(planet))
`when`(service.getFilm(anyString())).thenReturn(Single.just(film))
viewModel.liveData.value.let {
assertThat(it, `is`(notNullValue()))
if (it is Resource.Success) {
it.data?.let { data ->
assertTrue(data.films.isEmpty())
assertTrue(data.species.isEmpty())
}
}
}
}
@Test
fun givenServerResponseError_whenFetch_specie_shouldReturnError() {
`when`(service.getSpecie(anyString())).thenReturn(Single.error(Exception("error")))
`when`(service.getPlanet(anyString())).thenReturn(Single.just(planet))
`when`(service.getFilm(anyString())).thenReturn(Single.just(film))
viewModel.liveData.value.let {
assertThat(it, `is`(notNullValue()))
if (it is Resource.Error) {
assertThat(it.message, `is`(notNullValue()))
assertThat(it.message, `is`("error"))
}
}
}
}
Here is my ViewModel :
class DetailViewModel @Inject constructor(
schedulerProvider: BaseSchedulerProvider,
character: Character,
getSpecieUseCase: GetSpecieUseCase,
getPlanetUseCase: GetPlanetUseCase,
getFilmUseCase: GetFilmUseCase,
) : BaseViewModel<DetailWrapper>(schedulerProvider,
Single.zip(Flowable.fromIterable(character.specieUrls)
.flatMapSingle { specieUrl -> getSpecieUseCase(specieUrl) }
.flatMapSingle { specie ->
getPlanetUseCase(specie.homeWorld).map { planet ->
SpecieWrapper(specie.name, specie.language, planet.population)
}
}.toList(),
Flowable.fromIterable(character.filmUrls)
.flatMapSingle { filmUrl -> getFilmUseCase(filmUrl) }
.toList(), { species, films ->
DetailWrapper(species, films)
}))
And here is my BaseViewModel :
open class BaseViewModel<T>(
private val schedulerProvider: BaseSchedulerProvider,
private val singleRequest: Single<T>
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
init {
sendRequest()
}
fun sendRequest() {
_liveData.value = Resource.Loading
wrapEspressoIdlingResourceSingle { singleRequest }
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui()).subscribe({
_liveData.postValue(Resource.Success(it))
}) {
_liveData.postValue(Resource.Error(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}
And here is DetailWrapper class :
class DetailWrapper(
val species: List<SpecieWrapper>,
val films: List<Film>,
)
class SpecieWrapper(
val name: String,
val language: String,
val population: String,
)
Why films and species lists are empty in my local unit test?
As you see I pass two emptyLists to Character object. That is the source of problem since for instance I have following in DetailViewModel :
Flowable.fromIterable(character.filmUrls)
.flatMapSingle { filmUrl -> getFilmUseCase(filmUrl) }
.toList()
FilmUrls
is one of those emptyLists. If I change Character by passing not emptyList, it is working as expected :
character = Character("Ali", "127", "1385",
listOf("url1", "url2"), listOf("url1", "url2"))
I also need to move ViewModel initialization to the method body, such as :
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
`when`(repository.getSpecie(anyString())).thenReturn(Single.just(specie))
`when`(repository.getPlanet(anyString())).thenReturn(Single.just(planet))
`when`(repository.getFilm(anyString())).thenReturn(Single.just(film))
viewModel = DetailViewModel(schedulerProvider, character, GetSpecieUseCase(repository),
GetPlanetUseCase(repository), GetFilmUseCase(repository))
viewModel.liveData.value.let {
assertThat(it, `is`(notNullValue()))
if (it is Resource.Success) {
it.data?.let { data ->
assertTrue(data.films.isNotEmpty())
assertTrue(data.species.isNotEmpty())
}
}
}
}