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??
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.
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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With