什么是在Android单元测试中检查PagingData对象数据的正确方法?

21

我正在使用分页库从API中检索数据并在列表中显示它们。

为此,在我的存储库中创建了以下方法:

fun getArticleList(query: String): Flow<PagingData<ArticleHeaderData>>

在我的视图模型中,我创建了搜索方法,大致如下:

override fun search(query: String) {
    val lastResult = articleFlow
    if (query == lastQuery && lastResult != null)
        return
    lastQuery = query
    searchJob?.cancel()
    searchJob = launch {
        val newResult: Flow<PagingData<ArticleList>> = repo.getArticleList(query)
            .map {
                it.insertSeparators { //code to add separators }.cachedIn(this)
        articleFlow = newResult
        newResult.collectLatest {
            articleList.postValue(it)
        }
    }
}

为了测试我的视图模型,我正在使用测试方法PagingData.from来创建一个流,从我的模拟存储库中返回,如下所示: whenever(repo.getArticleList(query)).thenReturn(flowOf(PagingData.from(articles))) 然后,我从articleList LiveData中检索实际的分页数据,如下所示: val data = vm.articleList.value!! 这将返回一个PagingData<ArticleList>对象,我想验证它是否包含来自服务的数据(即由whenever返回的articles)。
我找到的唯一方法是创建以下扩展函数:
private val dcb = object : DifferCallback {
    override fun onChanged(position: Int, count: Int) {}
    override fun onInserted(position: Int, count: Int) {}
    override fun onRemoved(position: Int, count: Int) {}
}

suspend fun <T : Any> PagingData<T>.collectData(): List<T> {
    val items = mutableListOf<T>()
    val dif = object : PagingDataDiffer<T>(dcb, TestDispatchers.Immediate) {
        override suspend fun presentNewList(previousList: NullPaddedList<T>, newList: NullPaddedList<T>, newCombinedLoadStates: CombinedLoadStates, lastAccessedIndex: Int): Int? {
            for (idx in 0 until newList.size)
                items.add(newList.getFromStorage(idx))
            return null
        }
    }
    dif.collectFrom(this)
    return items
}

这个方法看起来可以工作,但是它基于标记为@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)PagingDataDiffer类,因此将来可能无法使用。

有没有更好的方法来获取PagingData中的flow(在库中标记为内部)或从中获取实际数据?


你有没有找到解决方案? - Myk
2
不,我还在使用这段代码,到目前为止还没有出现问题,所以我想它会一直保留。 - Cruces
@Cruces,这种方法似乎不再起作用了,当尝试使用扩展collectData()进行测试时,会抛出异常:在呈现新列表后缺少对onListPresentable的调用。如果您看到此异常,通常表示分页存在问题。请提交错误以便我们修复:https://issuetracker.google.com/issues/new?component=413106 - Ziv Kesten
2
在for循环之后只需调用onListPresentable(),@ZivKesten。 - Alvin Dizon
4个回答

9

我也曾遇到过Paging3库的同样问题,而网络上关于这个库的讨论并不多,但在查阅文档时我可能找到了解决方法。我面临的情景是尝试在PagingData中确定数据列表是否为空,然后根据此来操纵UI。

在文档中我找到了以下信息:在版本3.0.0-alpha04中添加了两个PagingDataAdapter的API: peek()snapshot()。其中,peek()基于索引给我们返回一个特定的列表对象,而snapshot()则给我们返回整个列表。

所以,我所做的就是:

lifecycleScope.launch {
    //Your PagingData flow submits the data to your RecyclerView adapter
    viewModel.allConversations.collectLatest {
        adapter.submitData(it)
    }
}
lifecycleScope.launch {
    //Your adapter's loadStateFlow here
    adapter.loadStateFlow.
        distinctUntilChangedBy {
            it.refresh
        }.collect {
            //you get all the data here
            val list = adapter.snapshot()
            ...
        }
    }

由于我最近才接触分页库和Flow,因此这种方法可能存在缺陷,请告知是否有更好的方式!


1
你的方法似乎对实际代码有效,我也设置了类似的东西(只是我使用我的viewmodel来保存pageddata的livedata,并在其中内部使用flow而不是暴露flow本身),但我正在尝试测试数据,在单元测试中,所以在你的例子中,你如何测试从viewmodel.allconversations.collectlatest接收到的内容? - Cruces
为什么要使用 distinctUntilChangedBy { it.refresh }?看起来它跳过了“appended”项目。 - CHAN

0

我同意王子健的观点,唯一测试它的方法是通过快照在此输入图片描述


3
我之前跟紫坚说过,这段代码在实际应用中有效。但是我现在正在运行单元测试,这些测试并没有生命周期作用域(lifecyclescope)和适配器(adapter),也没有运行robolectric来创建这些对象。这些测试只是为了测试基本功能而已。 - Cruces

0
你可以使用 AsyncPagingDataDiffer 来实现。
@Test
fun paging_data_should_not_be_empty()= runTest{
    
    val pagingData = viewModel.pager.first()

    val differ = AsyncPagingDataDiffer(
        diffCallback = TestDiffCallback<RecentChatUi>(),
        updateCallback = TestListCallback(),
        workerDispatcher = Dispatchers.Main
    )

    // You don't need to use launch() if you're using
    // PagingData.from()
    val job = launch {
        differ.submitData(pagingData)
    }

    advanceUntilIdle()
    val pagingList = differ.snapshot().items //this is the data you wanted
    assertTrue(pagingList.isEmpty().not())
    job.cancel()

}

创建必要的回调函数以传递AsyncPagingDataDiffer实例。
class TestListCallback : ListUpdateCallback {
    override fun onChanged(position: Int, count: Int, payload: Any?) {}
    override fun onMoved(fromPosition: Int, toPosition: Int) {}
    override fun onInserted(position: Int, count: Int) {}
    override fun onRemoved(position: Int, count: Int) {}
}

class TestDiffCallback<T> : DiffUtil.ItemCallback<T>() {
    override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }
}

-9

只有在有活动的互联网连接时才能正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接