Android – ¿Cómo probar Paging 3 (PagingSource)? | Autor: mohamed gamal | julio de 2021

Pagination 3 es una de las mejores bibliotecas de paginación: si le proporciona todo el contenido que queremos lograr, no solo se puede lograr la paginación con el menor esfuerzo, sino que también se pueden obtener datos de diferentes fuentes de datos (api / base de datos local) .
Pero por algunas razones, este también es un problema muy difícil:
- Si tiene una vista de lista sin paginación, no es fácil migrar su antigua base de código a Paging 3;
- ¿Qué partes del código se verán afectadas? emmmm Esta es una muy buena pregunta, por supuesto que la capa de datos no lo hará, pero
RepositoryLos nuevos cursos cambiaránPagingSource - Cómo probar
PagingSource? ! !
Aquí consigo reviews De algunos Api -> getReviews()
api.getReviews(
limit = params.loadSize,
offset = offset
)Esto es un ejemplo Api Respuesta ->
val reviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 0)
)Esta clase se usa para manejar la fuente de paginación de paginación usando la biblioteca paging 3:
class ReviewsPagingSource(private val api: Api) : PagingSource<Int, Review>() override suspend fun load(params: LoadParams<Int>) = try
// offset always starts with 0
val offset = params.key ?: 0
val response =
api.getReviews(
limit = params.loadSize,
offset = offset
) LoadResult.Page(
data = response.reviews.map Review(it) ,
prevKey =
if (response.pagination.offset == 0) null
else response.pagination.offset,
nextKey =
if (response.pagination.offset + response.reviews.size >= response.totalCount) null
else response.pagination.offset + response.reviews.size
)
catch (exception: Exception)
LoadResult.Error(exception)
Como notó aquí:
- No es necesario tener el tuyo propio
ResultUna clase que se usa para procesar respuestas y ayudar a desarrollar métodos generales de manejo de errores, porque la página 3 ha pasadoLoadResultsealed classEsto se encargará de esto. - Así que aquí hay 3 cosas interesantes que son difíciles de probar, manejo de errores,
offsetY los datos devueltos … una cosa más a tener en cuenta,loadLa función es unasuspendUno;)
Llama esto fuente, prefiero llamarlo throw a repo en lugar de llamarlo directamente desde el modelo de vista, así que creé este repositorio:
class ReviewsRepository(private val api: Api) fun getReviews() = Pager(
config = PagingConfig(
pageSize = REVIEWS_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory =
ReviewsPagingSource(api)
).flowcompanion object
private const val REVIEWS_PAGE_SIZE = 15
Este es un ejemplo de cómo probar ReviewsPagingSource:
@ExperimentalCoroutinesApi
class ReviewsPagingSourceTest @get:Rule
val instantExecutorRule = InstantTaskExecutorRule()@get:Rule
var coroutineTestRule = CoroutineTestRule()@Mock lateinit var api: Apilateinit var reviewsPagingSource: ReviewsPagingSource@Before
fun setup()
MockitoAnnotations.initMocks(this)
reviewsPagingSource = ReviewsPagingSource(api)
@Test
fun `reviews paging source load - failure - http error`() = runBlockingTest val error = RuntimeException("404", Throwable())
given(api.getReviews(any(), any())).willThrow(error)val expectedResult = PagingSource.LoadResult.Error<Int, Review>(error)assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source load - failure - received null`() = runBlockingTest given(api.getReviews(any(), any())).willReturn(null)val expectedResult = PagingSource.LoadResult.Error<Int, Review>(NullPointerException())assertEquals(
expectedResult.toString(), reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
).toString()
)
@Test
fun `reviews paging source refresh - success`() = runBlockingTest given(api.getReviews(any(), any())).willReturn(reviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = null,
nextKey = 1
)assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source append - success`() = runBlockingTest given(api.getReviews(any(), any())).willReturn(nextReviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = 1,
nextKey = 2
)assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Append(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source prepend - success`() = runBlockingTest given(api.getReviews(any(), any())).willReturn(reviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = null,
nextKey = 1
)assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Prepend(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
companion object val reviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 0)
) val nextReviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 1)
)
Me gusta analizar el código, si tiene alguna pregunta, no dude en preguntarme








