android - Android ViewModel 单元测试:RxJava onSuccess 给出 nullPointerException
问题描述
我正在尝试对我的 viewModel 类进行单元测试,但是当我运行测试时,我NullPointerException
在一次性OnSuccess
方法中得到了一个,但我不明白为什么。因此,我测试的方法总是返回 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)
解决方案
一旦这条线被调用:
// 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()
它在单独的线程上运行。在您的测试代码中,您需要使用TestScheduler来等待 io scheduler 中的操作完成。
边注:
在 ViewModels 中似乎.observeOn(AndroidSchedulers.mainThread())
没有做太多,因为 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()
如果上面的代码不起作用,也可以尝试模拟这些。
推荐阅读
- validation - 使用 JWKS golang 进行 JWT 验证
- javascript - 如何解决此按钮未显示的错误
- javascript - html2canvas 图像未保存
- google-cloud-platform - YouTube Data API v3:无需打开浏览器即可从服务器上传视频
- .htaccess - 通配符静默重定向到获取参数
- typescript - 在 TypeScript 的 Array 元素中描述接口 Fixed values
- flutter - SocketException(SocketException:操作系统错误:没有到主机的路由,errno = 113,地址 = 192.168.100.21,端口 = 48094)
- google-sheets - Zapier 在尝试将 google 表格与 slack 集成时不显示任何数据
- java - 从 MySQL 迁移到 PostgreSQL,Spring Boot
- javascript - 等待 AJAX 点击方法完成以执行另一个功能。如何实施?: Javascript, AJAX