为什么AsyncPagingDataDiffer调用submitData会导致单元测试冻结超时?
问题产生的核心原因
submitData本身的挂起特性AsyncPagingDataDiffer.submitData()是长期挂起的 suspend 方法,它会持续监听PagingData的分页加载事件、数据更新,除非协程被主动取消,否则不会主动返回。你直接在runTest的主线程调用它,测试会一直等待该方法返回,最终触发超时。你之前提交PagingData.from()构造的静态列表不会卡,是因为静态PagingData已预加载全部数据,没有后续分页任务,处理逻辑会快速走完。协程调度器不匹配
你构造PagingSourceFactory时指定了Dispatchers.Default,同时Paging组件内部默认会使用Android主调度器处理页面更新,而单元测试环境没有默认的主Looper,且runTest的虚拟时间控制逻辑默认不会调度其他调度器上的任务。从Room返回的PagingData是懒加载的,你调用first()只是拿到了流发射的PagingData外壳,实际的数据库查询、分页加载逻辑只有在调用submitData后才会触发,这些任务都跑在测试调度器管控外的线程上,永远不会被执行,导致submitData一直挂起。
解决方法
- 先添加调度器替换规则,把测试中用到的所有调度器都替换为测试调度器,确保
runTest可以控制所有协程任务的执行:
@OptIn(ExperimentalCoroutinesApi::class) class FooBarTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() // 替换主调度器为TestDispatcher } // 同时构造PagingSourceFactory时,测试环境下注入TestDispatcher,不要写死Dispatchers.Default
- 把
submitData放在单独的launch协程中执行,避免阻塞测试主线程,测试完成后主动取消该协程即可:
@ExperimentalCoroutinesApi @Test fun testGetFooBarsPaged(): Unit = runTest { // 省略differ初始化逻辑 val pagingData = fooBarsSdk.getFooBarsPaged(FooBar.Type, pageSize = 1).first() // 单独启动协程执行submitData val submitJob = launch { differ.submitData(pagingData) } // 等待数据加载完成后执行断言 while (differ.snapshot().isEmpty()) { delay(10) // 测试调度器下delay不会真的等待,只会推进虚拟时间 } // 此处添加你的断言逻辑 assertThat(differ.snapshot().size).isEqualTo(3) // 测试完成主动取消submitJob submitJob.cancel() }
- 提前给测试用的内存Room数据库插入对应测试数据,避免PagingSource加载空数据一直重试。
内容的提问来源于stack exchange,提问作者J. Hill
相关产品推荐
相关产品推荐

