【问题标题】:Android ViewModel unit test : RxJava onSuccess gives nullPointerExceptionAndroid ViewModel 单元测试:RxJava onSuccess 给出 nullPointerException
【发布时间】:2019-02-16 17:40:41
【问题描述】:

我正在尝试对我的 viewModel 类进行单元测试,但是当我运行测试时,我在一次性 OnSuccess 方法中得到了一个 NullPointerException,我不明白为什么。因此,我测试的方法总是返回 null。

这是我的测试类代码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)

【问题讨论】:

    标签: android unit-testing mockito rx-java2


    【解决方案1】:

    只要这条线被调用:

    // 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 scheduler中的操作完成。

    旁注:

    ViewModels 中的 .observeOn(AndroidSchedulers.mainThread()) 似乎在这里没有多大作用,因为 ViewModels 独立于 Android 生命周期。您可以使用postValue() 从后台线程更新MutableLiveData,而不是使用setValue()


    更新:

    试试这个:

    @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() 也可能是问题所在。如果上面的代码不起作用,也可以尝试模拟这些。

    【讨论】:

    • 我在 setUp() 方法中添加了这一行:RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } 现在我没有关于未调用 forecastRepo.getCache() 的错误,但我仍然在 onSuccess 中得到 NullPointerException
    • 我仍然收到错误,我认为它来自调度程序,当我调用 observeOn subscribeOn 但不确定也不知道为什么。请参阅上面更新后的帖子中的 stackTrace
    【解决方案2】:

    通过手动创建虚拟响应解决了问题。 getMockedCities() 返回具有空变量的 City 对象数组(如预期的那样),但我的 City 对象的变量不可为空。我还必须模拟 repository.getCache()

    【讨论】:

      猜你喜欢
      • 2019-12-04
      • 2021-01-07
      • 2016-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多