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

Mockito Mock无法返回分页/流数据问题排查

问题描述

我在Mock仓库返回的分页数据时遇到问题,需要测试ViewModel中的如下逻辑:

// watch for filter to change, on charge reset the paging query
val items = filterParameter.switchMap { filter ->
    Pager(
        config = PagingConfig(pageSize = 20),
        initialKey = null,
        pagingSourceFactory = {
            repository.observeItems(filter)
        }
    ).liveData
}.asFlow().map { pagingData ->
    pagingData
        .map {
            ListItem(it)
        }
        .insertSeparators { item1: ListItem?, item2: ListItem? ->
            // 要测试的分隔符逻辑
        }
}.cachedIn(viewModelScope)

测试代码如下:

@Test
fun testTransformation() = runTest {
    val repository = mock<MyRepository>()
    `when`(repository.observeItems(Mockito.any()))
        .thenAnswer {
            val flow = flowOf(
                listOf(
                    Item(
                        id = "1",
                        date = Date()
                    )
                )
            )
            val pagingSourceFactory = flow.asPagingSourceFactory(coroutineScope = this)
            pagingSourceFactory()
        }

    val viewModel = MyViewModel(
        repository = repository,
    )

    val items: Flow<PagingData<ListItem>> = viewModel.items
    val snapshot: List<ListItem> = items.asSnapshot() {}

    Assert.assertEquals(1, snapshot.size) // 失败,快照大小为0而非预期的1
}

已确认测试时ViewModel中的Pager已初始化,且Mock Flow已被调用,但无法获取Mock数据,断言快照大小为0,求解决思路。

解决思路
  • 触发filterParameter发射值:ViewModel中items是通过filterParameter.switchMap创建的,如果filterParameter没有发射任何值,整个流不会启动。测试时需要给filterParameter设置一个有效过滤值,示例:

    // 假设filterParameter是MutableStateFlow<Filter>类型
    viewModel.filterParameter.value = Filter()
    
  • 修正Mock的PagingSource创建方式:flow.asPagingSourceFactory依赖的协程作用域可能与测试环境不兼容,建议直接返回一个预定义数据的PagingSource实现,示例:

    `when`(repository.observeItems(Mockito.any()))
        .thenReturn(object : PagingSource<Int, Item>() {
            override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
                return LoadResult.Page(
                    data = listOf(Item(id = "1", date = Date())),
                    prevKey = null,
                    nextKey = null
                )
            }
    
            override fun getRefreshKey(state: PagingState<Int, Item>): Int? = null
        })
    
  • 正确收集分页数据:asSnapshot可能无法正确等待分页数据加载完成,建议改用collectLatest配合列表收集数据,并调用advanceUntilIdle()确保协程执行完毕,示例:

    val collectedItems = mutableListOf<ListItem>()
    val job = launch {
        items.collectLatest { pagingData ->
            pagingData.collect { item ->
                collectedItems.add(item)
            }
        }
    }
    advanceUntilIdle()
    job.cancel()
    Assert.assertEquals(1, collectedItems.size)
    
  • 处理cachedIn的作用域问题:cachedIn(viewModelScope)会将数据缓存到ViewModel的作用域中,测试时需确保viewModelScope使用测试兼容的调度器,可在创建ViewModel时传入测试用的CoroutineScope。

内容的提问来源于stack exchange,提问作者lostintranslation

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 21:15:35