【问题标题】:Unit test a ViewModel method that uses ViewModelScope.launch to call a suspend function with delay单元测试使用 ViewModelScope.launch 调用具有延迟的挂起函数的 ViewModel 方法
【发布时间】:2019-11-19 19:48:40
【问题描述】:

在我的ViewModel 中,我有一个onTextChanged 方法,该方法是通过EditText 上的数据绑定调用的。在那里我使用viewModelScope.launch{} 来启动suspend 函数。在那个函数中,我延迟了 500 毫秒。现在如何测试onTextChanged 方法?我尝试的一切总是在延迟完成之前完成测试。我试过runBlockingTestTestCoroutineDispatcherrunBlocking

文本观察者:

override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        searchJob = if (searchJob?.isActive == true) {
            searchJob?.cancel()
            viewModelScope.launch { search(s.toString()) }
        } else {
            viewModelScope.launch { search(s.toString()) }
        }
    }

搜索方式:

 private suspend fun search(query: String) {
            delay(500)
            searchUseCase(SearchParams(query)).fold({

            }, {

            })
    }

测试:

    @Test
    fun `Search is fired after 500ms when text is changed`()  = runBlockingTest {
        val viewModel = ViewModel(useCase)
        viewModel.onTextChanged("test", 0, 0, 0)

        //TODO assert time was 500ms or more

        //This fails
        coVerify(exactly = 1) { useCase.invoke(any()) }
    }

【问题讨论】:

  • 这听起来可能很愚蠢,但是您是否尝试在测试中添加 500-1000 毫秒的睡眠时间以查看它是否开始通过?测试本身可能是错误的。
  • 这很遗憾没有帮助
  • 这里有什么更新吗?
  • 你解决了这个问题吗?

标签: android unit-testing kotlin-coroutines


【解决方案1】:

google 的建议是,您应该以某种方式将CoroutineDispatcher 注入您的视图模型,以便您可以在测试期间对其进行更改。

class MainViewModel(
    private val dispatcher: CoroutineDispatcher = Dispatchers.Default
) : ViewModel() {

    private var _userData: MutableLiveData<Any> = MutableLiveData<Any>()
    val userData: LiveData<Any> = _userData

    fun savedDelayed() = viewModelScope.launch {
        delay(1000)
        saveSessionData()
    }

    suspend fun saveSessionData() {
        viewModelScope.launch(dispatcher) {
            _userData.value = "some_user_data"
        }
    }
}

@ExperimentalCoroutinesApi
class MainViewModelTest {

    private val testDispatcher = TestCoroutineDispatcher()

    @ExperimentalCoroutinesApi
    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Test
    fun testsSaveSessionData() = runBlockingTest {
        val mainViewModel = MainViewModel(testDispatcher)
      
        mainViewModel.savedDelayed()
        testDispatcher.advanceUntilIdle()
      
        val userData = mainViewModel.userData.value
        assertEquals("some_user_data", userData)
    }

}

从中型文章中粘贴的代码,针对您的用例进行了修改。请阅读文章以获得进一步的解释: https://medium.com/swlh/unit-testing-with-kotlin-coroutines-the-android-way-19289838d257

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    • 2021-10-23
    • 1970-01-01
    相关资源
    最近更新 更多