测试视图模型时获取实时数据最新值
Get livedata latest value while testing view model
我正在尝试对我的视图模型进行单元测试:
private val loginRepository: LoginRepository = LoginRepository()
private val _loginSuccess = MutableLiveData<Resource<String>>()
val loginSuccess : LiveData<Resource<String>>
get() = _loginSuccess
fun login(credentials : RequestLogin){
_loginSuccess.value = Resource.loading()
viewModelScope.launch {
_loginSuccess.postValue(loginRepository.login(credentials))
}
有了这个:
@Test
fun login_success(){
val loginRequest = RequestLogin("username", "test")
val app:Application = ApplicationProvider.getApplicationContext()
PreferencesHelper.init(app)
val viewModel = LoginViewModel(app)
viewModel.loginSuccess.observeForever(dataObserver)
runBlocking {
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
}
viewModel.loginSuccess.removeObserver(dataObserver)
}
但每次我只得到 liveData 对象的第一个值 Resource.loading() 而不是使用 postValue 方法获得的值。
如何忽略第一个 liveData 更新的结果而只获取最后一个?
runBlocking
执行并等待您传递给它的块的完成,在本例中是
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
但是这段代码没有任何挂起调用,所以runBlocking
在这里没有任何作用。特别是它不会影响 viewModelScope.launch
调用。
有几种方法可以测试此代码。我建议使用 kotlinx-coroutines-test library. It provides TestCoroutineDispatcher,这在这种情况下非常方便。
viewModelScope
默认使用Dispatchers.Main
dispatcher,需要替换成TestCoroutineDispatcher
。例如。您可以创建一个简单的测试规则:
class CoroutineTestRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
然后将其应用到您的测试中:
@get:Rule
var coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
然后像这样使用它
@Test
fun login_success(){
...
viewModel.login(loginRequest)
coroutineTestRule.dispatcher.advanceUntilIdle()
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
...
}
下面是它的一些工作原理:
CoroutineTestRule
将 Dispatcher.Main
替换为 CoroutineTestRule.dispatcher
- 您的视图模型使用
viewModelScope
启动登录作业,它使用相同的 CoroutineTestRule.dispatcher
coroutineTestRule.dispatcher.advanceUntilIdle()
使调度程序执行所有未完成的任务,因此它将执行所有使用此调度程序并准备执行的协程。
TestCoroutineDispatcher
上还有非常方便的 advanceTimeBy
方法,可以让您快进和跳过,例如延迟通话。
我正在尝试对我的视图模型进行单元测试:
private val loginRepository: LoginRepository = LoginRepository()
private val _loginSuccess = MutableLiveData<Resource<String>>()
val loginSuccess : LiveData<Resource<String>>
get() = _loginSuccess
fun login(credentials : RequestLogin){
_loginSuccess.value = Resource.loading()
viewModelScope.launch {
_loginSuccess.postValue(loginRepository.login(credentials))
}
有了这个:
@Test
fun login_success(){
val loginRequest = RequestLogin("username", "test")
val app:Application = ApplicationProvider.getApplicationContext()
PreferencesHelper.init(app)
val viewModel = LoginViewModel(app)
viewModel.loginSuccess.observeForever(dataObserver)
runBlocking {
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
}
viewModel.loginSuccess.removeObserver(dataObserver)
}
但每次我只得到 liveData 对象的第一个值 Resource.loading() 而不是使用 postValue 方法获得的值。
如何忽略第一个 liveData 更新的结果而只获取最后一个?
runBlocking
执行并等待您传递给它的块的完成,在本例中是
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
但是这段代码没有任何挂起调用,所以runBlocking
在这里没有任何作用。特别是它不会影响 viewModelScope.launch
调用。
有几种方法可以测试此代码。我建议使用 kotlinx-coroutines-test library. It provides TestCoroutineDispatcher,这在这种情况下非常方便。
viewModelScope
默认使用Dispatchers.Main
dispatcher,需要替换成TestCoroutineDispatcher
。例如。您可以创建一个简单的测试规则:
class CoroutineTestRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
然后将其应用到您的测试中:
@get:Rule
var coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
然后像这样使用它
@Test
fun login_success(){
...
viewModel.login(loginRequest)
coroutineTestRule.dispatcher.advanceUntilIdle()
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
...
}
下面是它的一些工作原理:
CoroutineTestRule
将Dispatcher.Main
替换为CoroutineTestRule.dispatcher
- 您的视图模型使用
viewModelScope
启动登录作业,它使用相同的CoroutineTestRule.dispatcher
coroutineTestRule.dispatcher.advanceUntilIdle()
使调度程序执行所有未完成的任务,因此它将执行所有使用此调度程序并准备执行的协程。
TestCoroutineDispatcher
上还有非常方便的 advanceTimeBy
方法,可以让您快进和跳过,例如延迟通话。