测试视图模型时获取实时数据最新值

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.Maindispatcher,需要替换成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"))
   ...
}

下面是它的一些工作原理:

  1. CoroutineTestRuleDispatcher.Main 替换为 CoroutineTestRule.dispatcher
  2. 您的视图模型使用 viewModelScope 启动登录作业,它使用相同的 CoroutineTestRule.dispatcher
  3. coroutineTestRule.dispatcher.advanceUntilIdle() 使调度程序执行所有未完成的任务,因此它将执行所有使用此调度程序并准备执行的协程。

TestCoroutineDispatcher 上还有非常方便的 advanceTimeBy 方法,可以让您快进和跳过,例如延迟通话。