What is the correct way to check the data from a PagingData object in Android Unit Tests

Viewed 4551

I am using paging library to retrieve data from an api and show them in a list

for this purpose in my repository I have created the method:

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

in my viewmodel I have created the search method which goes something like this:

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)
        }
    }
}

in order to test my viewmodel I am using the test method PagingData.from to create a flow to return from my mocked repository like so:

whenever(repo.getArticleList(query)).thenReturn(flowOf(PagingData.from(articles)))

and then I retrieve the actual paging data from the articleList LiveData like so:

val data = vm.articleList.value!!

this returns a PagingData<ArticleList> object that I would like to verify it contains the data from the service (i.e. the articles returned by whenever)

the only way I have found to do this is by creating the following extension function:

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
}

which seems to work, but is based on the PagingDataDiffer class which is marked as @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) so it may not work in the future

is there a better way to either get the flow from PagingData (which is marked as internal in the library) or get the actual data from it?

3 Answers

I've had the same problem with the Paging3 library, and there not a lot discussions about this library online yet, but as I digging through some the docs, I may found a solution. The scenario I'm facing is trying to determine whether a data list is empty or not within PagingData, then I'll manipulate the UI base on that.

Here's what I found in the doc, there are two apis in PagingDataAdapter that have been added in version 3.0.0-alpha04 which is peek(), and snapshot(), peek() gives us a specific list object based on index, whereas snapshot() gives us the whole list.

So here's what I've done:

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()
            ...
        }
    }

Since I just get my hands on the Paging library and Flow recently, there might be flaws with this approach, let me know if there are better ways!

I agree with Zijian Wang the only way to test it, is through snapshot enter image description here

This only works when there is an active internet connection.

Related