Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I Unit test Paging 3(PagingSource)?

Google recently announced the new Paging 3 library, Kotlin-first library, Support for coroutines and Flow...etc.

I played with the codelab they provide but it seems there's not any support yet for testing, I also checked documentation. They didn't mention anything about testing, So For Example I wanted to unit test this PagingSource:

 class GithubPagingSource(private val service: GithubService,
                     private val query: String) : PagingSource<Int, Repo>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
    //params.key is null in loading first page in that case we would use constant GITHUB_STARTING_PAGE_INDEX
    val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
    val apiQuery = query + IN_QUALIFIER
    return try {
        val response = service.searchRepos(apiQuery, position, params.loadSize)
        val data = response.items
        LoadResult.Page(
                        data,
                        if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                        if (data.isEmpty()) null else position + 1)
    }catch (IOEx: IOException){
        Log.d("GithubPagingSource", "Failed to load pages, IO Exception: ${IOEx.message}")
        LoadResult.Error(IOEx)
    }catch (httpEx: HttpException){
        Log.d("GithubPagingSource", "Failed to load pages, http Exception code: ${httpEx.code()}")
        LoadResult.Error(httpEx)
    }
  }
}  

So, How can I test this, is anyone can help me??

like image 699
MR3YY Avatar asked Jun 19 '20 15:06

MR3YY


People also ask

How do I test my paging system?

Test by dialing – Dial the paging access, or feature code, having your Butt-Sett on speaker-phone will allow you to hear the page through it. if you get busied signals, then check the port designated for paging, and run the maintenance commands. If all fails, then assign a new port, or replace the defective board.


Video Answer


2 Answers

I'm currently having a similar experience of finding out that the paging library isn't really designed to be testable. I'm sure Google will make it more testable once it's a more mature library.

I was able to write a test for PagingSource. I used the RxJava 3 plugin and mockito-kotlin, but the general idea of the test should be reproducible with the Coroutines version of the API and most testing frameworks.

class ItemPagingSourceTest {

    private val itemList = listOf(
            Item(id = "1"),
            Item(id = "2"),
            Item(id = "3")
    )

    private lateinit var source: ItemPagingSource

    private val service: ItemService = mock()

    @Before
    fun `set up`() {
        source = ItemPagingSource(service)
    }

    @Test
    fun `getItems - should delegate to service`() {
        val onSuccess: Consumer<LoadResult<Int, Item>> = mock()
        val onError: Consumer<Throwable> = mock()
        val params: LoadParams<Int> = mock()

        whenever(service.getItems(1)).thenReturn(Single.just(itemList))
        source.loadSingle(params).subscribe(onSuccess, onError)

        verify(service).getItems(1)
        verify(onSuccess).accept(LoadResult.Page(itemList, null, 2))
        verifyZeroInteractions(onError)
    }
}

It's not perfect, since verify(onSuccess).accept(LoadResult.Page(itemList, null, 2)) relies on LoadResult.Page being a data class, which can be compared by the values of its properties. But it does test PagingSource.

like image 84
Adrian Czuczka Avatar answered Oct 09 '22 13:10

Adrian Czuczka


There is a way to do that with AsyncPagingDataDiffer

Step 1. Create DiffCallback

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

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

Step 2. Create ListCallback

class NoopListCallback : 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) {}
}

Step 3. Submit data to the differ and take the screenshot

@Test
    fun WHEN_init_THEN_shouldGetEvents_AND_updateUiModel() {
        coroutineDispatcher.runBlockingTest {
            val eventList = listOf(FavoriteEvent(ID, TITLE, Date(1000), URL))
            val pagingSource = PagingData.from(eventList)

            val captureUiModel = slot<PagingData<FavoriteEventUiModel>>()
            every { uiModelObserver.onChanged(capture(captureUiModel)) } answers {}
            coEvery { getFavoriteUseCase.invoke() } returns flowOf(pagingSource)

            viewModel.uiModel.observeForever(uiModelObserver)

            val differ = AsyncPagingDataDiffer(
                diffCallback = DiffFavoriteEventCallback(),
                updateCallback = NoopListCallback(),
                workerDispatcher = Dispatchers.Main
            )

            val job = launch {
                viewModel.uiModel.observeForever {
                    runBlocking {
                        differ.submitData(it)
                    }
                }
            }

            val result = differ.snapshot().items[0]
            assertEquals(result.id, ID)
            assertEquals(result.title, TITLE)
            assertEquals(result.url, URL)

            job.cancel()

            viewModel.uiModel.removeObserver(uiModelObserver)
        }
    }

Documentation https://developer.android.com/reference/kotlin/androidx/paging/AsyncPagingDataDiffer

like image 1
anna_manzhula Avatar answered Oct 09 '22 11:10

anna_manzhula