Модульный тест Android ViewModel: RxJava onSuccess дает исключение nullPointerException - PullRequest
0 голосов
/ 16 февраля 2019

Я пытаюсь выполнить модульное тестирование своего класса viewModel, но при запуске теста я получаю исключение NullPointerException в своем одноразовом методе OnSuccess, и я не понимаю, почему.Из-за этого метод, который я тестирую, всегда возвращает нуль.

Вот мой код для моего тестового класса:

CityListViewModelTest.kt

@RunWith(JUnit4::class)
class CityListViewModelTest {

    @Rule
    @JvmField
    val rule = InstantTaskExecutorRule()

    @Mock
    private lateinit var repository: ForecastRepository

    @InjectMocks
    private lateinit var viewModel: CityListViewModel


    @Before @Throws fun setUp(){
        RxAndroidPlugins.setInitMainThreadSchedulerHandler{Schedulers.trampoline()}
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }

        MockitoAnnotations.initMocks(this)

    }

    @Test
    fun getCities() {
        val response = getMockedCities(5)

        `when`(repository.getCities(ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble()))
            .thenReturn(Single.just(response))

        val result = viewModel.getCities(0.0,0.0)

        verify(repository).getCities(0.0,0.0)
        verify(repository).getCache() //should be called but isn't

        assertEquals(response.list,result.value) //result.value should be a list of 5 mocked cities but is null

    }

    fun getMockedCities(count : Int) : OpenWeatherCycleDataResponse {
        val cities = ArrayList<City>()
        for (i in 0..count) {
            val city = mock(City::class.java)
            cities.add(city)
        }
        return OpenWeatherCycleDataResponse(cities)
    }
}

И мой класс viewModel:

CityListViewModel.kt

class CityListViewModel @Inject constructor(private var forecastRepo: ForecastRepository):ViewModel() {
    //@Inject lateinit
    var cities : MutableLiveData<List<City>> = MutableLiveData()
    //@Inject lateinit
     var disposable : CompositeDisposable = CompositeDisposable()


    fun getCities(lat: Double,lon:Double): LiveData<List<City>> {
        disposable.add(forecastRepo.getCities(lat,lon).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(object: DisposableSingleObserver<OpenWeatherCycleDataResponse>(){
                override fun onSuccess(t: OpenWeatherCycleDataResponse) {

                    forecastRepo.getCache().saveCities(t.list)
                    cities.value = t.list
                }

                override fun onError(e: Throwable) {
                    Timber.e(e.localizedMessage)
                }
            }))
        return cities
    }

    fun getCityByName(cityName: String): LiveData<City>{
        val searchedCity = MutableLiveData<City>()
        disposable.add(forecastRepo.getCityByName(cityName).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeWith(object: DisposableSingleObserver<City>(){
                override fun onSuccess(t: City) {

                    searchedCity.value = t
                    forecastRepo.getCache().saveCities(listOf(t))
                }

                override fun onError(e: Throwable) {
                    Timber.e(e.localizedMessage)
                }
            }))
        return searchedCity
    }

    override fun onCleared() {
        super.onCleared()
        disposable.clear()
    }
}

Вот журналы:

java.lang.NullPointerException
    at com.example.zach.weatherapp.viewModel.CityListViewModel$getCities$1.onSuccess(CityListViewModel.kt:30)
    at com.example.zach.weatherapp.viewModel.CityListViewModel$getCities$1.onSuccess(CityListViewModel.kt:27)
    at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.run(SingleObserveOn.java:81)
    at io.reactivex.internal.schedulers.TrampolineScheduler.scheduleDirect(TrampolineScheduler.java:52)
    at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.onSuccess(SingleObserveOn.java:64)
    at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
    at io.reactivex.internal.operators.single.SingleJust.subscribeActual(SingleJust.java:30)
    at io.reactivex.Single.subscribe(Single.java:3096)
    at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
    at io.reactivex.internal.schedulers.TrampolineScheduler.scheduleDirect(TrampolineScheduler.java:52)
    at io.reactivex.internal.operators.single.SingleSubscribeOn.subscribeActual(SingleSubscribeOn.java:37)
    at io.reactivex.Single.subscribe(Single.java:3096)
    at io.reactivex.internal.operators.single.SingleObserveOn.subscribeActual(SingleObserveOn.java:35)
    at io.reactivex.Single.subscribe(Single.java:3096)
    at io.reactivex.Single.subscribeWith(Single.java:3140)
    at com.example.zach.weatherapp.viewModel.CityListViewModel.getCities(CityListViewModel.kt:27)
    at com.example.zach.weatherapp.viewModel.CityListViewModelTest.getCities(CityListViewModelTest.kt:58)

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Проблема решена созданием вручную фиктивного ответа.getMockedCities () возвращает массив объектов City с нулевыми переменными (как и ожидалось), но переменные моего объекта City не обнулялись.Также мне пришлось издеваться над repository.getCache ()

0 голосов
/ 17 февраля 2019

Как только эта строка будет вызвана:

// CityListViewModelTest
val result = viewModel.getCities(0.0,0.0)

CityListViewModel подпишется на forecastRepo.getCities(), поэтому имеет смысл, что verify(repository).getCities(0.0,0.0) проходит.

Однако это не такгарантируется, что forecastRepo.getCache() будет вызван раньше verify(repository).getCache(), потому что forecastRepo.getCities() работает в отдельном потоке.В вашем тестовом коде вам нужно использовать TestSchedulers для ожидания завершения операции в планировщике io.

Примечание:

Кажется, .observeOn(AndroidSchedulers.mainThread()) в ViewModels не делаетздесь не так много, потому что ViewModels не зависят от жизненных циклов Android.Вместо setValue() вы можете использовать postValue() для обновления MutableLiveData из фонового потока.


Обновление:

Попробуйте использовать это:

@Rule
@JvmField
val rule = InstantTaskExecutorRule()

@Mock
lateinit var observer: Observer<List<City>>

@Test
fun getCities() {
    val response = getMockedCities(5)

    `when`(repository.getCities(ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble()))
        .thenReturn(Single.just(response))

    viewModel.getCities(0.0,0.0).observeForever(observer)

    verify(repository).getCities(0.0,0.0)
    verify(repository).getCache()

    // assertEquals(response.list,result.value) //result.value should be a list of 5 mocked cities but is null
    verify(observer).onChanged(reponse.list)
}

Также могут быть проблемы с getCache() и getCache().saveCities().Попытайтесь смоделировать их, если приведенный выше код не работает.

...