You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:02:03