使用 RxJava 时对 LiveData 进行本地单元测试

时间:2021-03-19 07:53:08

标签: android unit-testing rx-java rx-java2

完整源代码位于:https://github.com/Ali-Rezaei/StarWarsSearch-RxPaging

我正在尝试测试我的 DetailViewModel。我的期望是物种和电影不是空列表,例如:when(service.getSpecie(anyString())).thenReturn(Single.just(specie))。这是我的测试:

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"))
        }
    }
}
}

这是我的视图模型:

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)
        }))

这是我的 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()
    }
}

这里是 DetailWrapper 类:

class DetailWrapper(
    val species: List<SpecieWrapper>,
    val films: List<Film>,
)

class SpecieWrapper(
    val name: String,
    val language: String,
    val population: String,
)

为什么我的本地单元测试中电影和物种列表是空的?

1 个答案:

答案 0 :(得分:0)

如您所见,我将两个空列表传递给 Character 对象。这是问题的根源,因为例如我在 DetailViewModel 中有以下内容:

Flowable.fromIterable(character.filmUrls)
                        .flatMapSingle { filmUrl -> getFilmUseCase(filmUrl) }
                        .toList()

FilmUrls 是那些空列表之一。如果我通过传递 not emptyList 来更改 Character,它会按预期工作:

character = Character("Ali", "127", "1385",
                listOf("url1", "url2"), listOf("url1", "url2"))

我还需要将ViewModel初始化移动到方法体中,比如:

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