测试Flow<PagingData<T>>.cachedIn时出现未完成协程错误
问题原因
cachedIn(viewModelScope)会把分页Flow的缓存逻辑绑定到viewModelScope下的协程中。在测试场景里,runTest会在测试结束时检查所有子协程是否已完成,但默认情况下ViewModel的viewModelScope不会在测试结束时自动取消(因为测试环境不会触发ViewModel的onCleared()方法),这就导致cachedIn创建的缓存协程一直处于活跃状态,最终抛出UncompletedCoroutinesError。
解决办法
方法1:测试结束前手动取消ViewModelScope
在测试断言完成后,主动调用viewModelScope.cancel()来终止所有关联的活跃协程:
@Test fun `getPagedBestRatedMoviesUseCase is called in constructor`() = runTest { val data = viewModel.bestRatedMovies.asSnapshot() assertEquals(mockList, data) // 手动取消viewModelScope,清理cachedIn绑定的协程 viewModel.viewModelScope.cancel() }
方法2:给ViewModel注入测试用的CoroutineScope
修改ViewModel的构造函数,允许传入自定义CoroutineScope。测试时直接传入runTest提供的TestScope,测试结束时TestScope会自动取消,无需手动处理:
ViewModel代码调整
class MovieViewModel( private val getPagedBestRatedMoviesUseCase: GetPagedBestRatedMoviesUseCase, // 允许外部传入CoroutineScope,默认使用viewModelScope private val coroutineScope: CoroutineScope = viewModelScope ) : ViewModel() { val bestRatedMovies = getPagedBestRatedMoviesUseCase() .cachedIn(coroutineScope) }
测试代码
@Test fun `getPagedBestRatedMoviesUseCase is called in constructor`() = runTest { // 传入当前TestScope作为ViewModel的协程范围 val viewModel = MovieViewModel(mockUseCase, this) val data = viewModel.bestRatedMovies.asSnapshot() assertEquals(mockList, data) }
内容的提问来源于stack exchange,提问作者alGhul7
相关产品推荐
相关产品推荐

