什么是paging3
paging3是一个喷气背包库,它允许我们轻松地从数据源(本地,远程,文件等)加载大型数据集。它逐渐加载数据,减少网络和系统资源的使用情况。它是用Kotlin编写的,并与其他JetPack库进行协调。
依赖性
首先,我们必须将此依赖性添加到我们的 build.gradle
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
implementation "androidx.paging:paging-common-ktx:3.1.1"
testImplementation "io.mockk:mockk:1.12.5"
testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:1.12.5"
分页源文件
所以我们有 SpecialationPagingsource 这样:
class SpecializationPagingSource(
private val professionUid: String,
private val query: SpecializationQuery,
private val api: CommonService
) : PagingSource<Int, SpecializationDomain>() {
companion object {
private const val STARTING_PAGE = 1
}
override fun getRefreshKey(state: PagingState<Int, SpecializationDomain>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, SpecializationDomain> {
val currentPage = params.key.takeIf { it != 0 } ?: STARTING_PAGE
return try {
val result = withContext(Dispatchers.IO) {
ApiHandler.handleApi {
api.getSpecializationList(professionUid = professionUid, query = query.toMap())
}
}
val totalPages = result?.meta?.pagination?.totalPage ?: 0
val data = result?.data?.record ?: listOf()
LoadResult.Page(
data = data.map { it.toDomain() },
prevKey = null,
nextKey = if (currentPage < totalPages) currentPage + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
让我们写出测试用例:)
积极的情况
在积极的情况下,我们可以用 pagingsource loadResult.page 返回值。例如:
LoadResult.Page(data = null, prevKey = null, nextKey = null)
我们有2个针对 pagingsource 的测试方案。 刷新和附加。 刷新是当我们的 pagingsource 负载第一页,而 append 是我们的 pagingsource load load load next next pages。
对于刷新测试,我们可以像这样编写测试:
//given
val fakeResponse = GeneralResponseWrapper(
data = GeneralRecordHolder(record = listOf(specializationResponse)),
meta = GeneralMetaResponse(
pagination = pagination = GeneralPaginationResponse(
page = 1,
totalPage = 1,
limit = 1,
totalRecords = 1,
records = 1
)
)
)
val expectedResult =
PagingSource.LoadResult.Page(
data = listOf(specializationResponse).map { it.toDomain() },
prevKey = null,
nextKey = null
)
//when
coEvery {
mockService.getSpecializationList(any(), any())
} returns Response.success(fakeResponse)
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
请查看通用paginationResponse 类。我们设置 totalpage = 1 ,因为我们只想在第一次分页加载数据时进行测试。然后,我们使用 pagingsource.loadparams.refresh 用于刷新。
对于附加测试,我们可以像这样编写测试:
//given
val fakeResponse = GeneralResponseWrapper(
data = GeneralRecordHolder(record = listOf(specializationResponse)),
meta = GeneralMetaResponse(
pagination = GeneralPaginationResponse(
page = 1,
totalPage = 2,
limit = 1,
totalRecords = 1,
records = 1
)
)
)
val expectedResult =
PagingSource.LoadResult.Page(
data = listOf(specializationResponse).map { it.toDomain() },
prevKey = null,
nextKey = 2
)
//when
coEvery {
mockService.getSpecializationList(any(), any())
} returns Response.success(fakeResponse)
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Append(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
请查看通用paginationResponse 类。我们设置 totalpage = 2 ,因为我们希望分页上有2页要加载 limit = 1 每个页面。请注意预期的变量。下一个 key = 2 表示我们期望分页负载页。然后,我们使用 pagingsource.loadparams.append 将下一页附加到现有加载数据。
否定案例
在负面情况下,我们可以用 pagingsource loadResult.error 返回值。例如:
PagingSource.LoadResult.Error<Int, T>(Exception("error data"))
,我们的代码看起来像这样:
//given
val expectedResult =
PagingSource.LoadResult.Error<Int, SpecializationDomain>(BadRequestException("error data"))
//when
coEvery {
mockService.getSpecializationList(any(), any())
} throws expectedResult.throwable
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
示例代码:
class SpecializationPagingSource(
private val professionUid: String,
private val query: SpecializationQuery,
private val api: CommonService
) : PagingSource<Int, SpecializationDomain>() {
companion object {
private const val STARTING_PAGE = 1
}
override fun getRefreshKey(state: PagingState<Int, SpecializationDomain>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, SpecializationDomain> {
val currentPage = params.key.takeIf { it != 0 } ?: STARTING_PAGE
return try {
val result = withContext(Dispatchers.IO) {
ApiHandler.handleApi {
api.getSpecializationList(professionUid = professionUid, query = query.toMap())
}
}
val totalPages = result?.meta?.pagination?.totalPage ?: 0
val data = result?.data?.record ?: listOf()
LoadResult.Page(
data = data.map { it.toDomain() },
prevKey = null,
nextKey = if (currentPage < totalPages) currentPage + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
class SpecializationPagingSourceTest : BaseUnitTestDataLayer() {
lateinit var specializationPagingSource: SpecializationPagingSource
@MockK
lateinit var mockService: CommonService
@RelaxedMockK
lateinit var specializationQuery: SpecializationQuery
companion object {
val specializationResponse = SpecializationResponse(
"1",
"dokter hewan"
)
}
@Before
override fun setUp() {
super.setUp()
specializationPagingSource =
SpecializationPagingSource("thisIsUid", specializationQuery, mockService)
}
@Test
fun `when SpecializationPagingSource refresh return Success`() = runTest {
//given
val fakeResponse = GeneralResponseWrapper(
data = GeneralRecordHolder(record = listOf(specializationResponse)),
meta = GeneralMetaResponse(
pagination = pagination = GeneralPaginationResponse(
page = 1,
totalPage = 2,
limit = 1,
totalRecords = 1,
records = 1
)
)
)
val expectedResult =
PagingSource.LoadResult.Page(
data = listOf(specializationResponse).map { it.toDomain() },
prevKey = null,
nextKey = null
)
//when
coEvery {
mockService.getSpecializationList(any(), any())
} returns Response.success(fakeResponse)
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
}
@Test
fun `when SpecializationPagingSource append return Success`() = runTest {
//given
val fakeResponse = GeneralResponseWrapper(
data = GeneralRecordHolder(record = listOf(specializationResponse)),
meta = GeneralMetaResponse(
pagination = GeneralPaginationResponse(
page = 1,
totalPage = 2,
limit = 1,
totalRecords = 1,
records = 1
)
)
)
val expectedResult =
PagingSource.LoadResult.Page(
data = listOf(specializationResponse).map { it.toDomain() },
prevKey = null,
nextKey = 2
)
//when
coEvery {
mockService.getSpecializationList(any(), any())
} returns Response.success(fakeResponse)
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Append(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
}
@Test
fun `when SpecializationPagingSource return Exception`() = runTest {
//given
val expectedResult =
PagingSource.LoadResult.Error<Int, SpecializationDomain>(BadRequestException("error data"))
//when
coEvery {
mockService.getSpecializationList(any(), any())
} throws expectedResult.throwable
//then
Assert.assertEquals(
expectedResult,
specializationPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
coVerify {
mockService.getSpecializationList(any(), any())
}
}
}
参考:https://medium.com/@mohamed.gamal.elsayed/android-how-to-test-paging-3-pagingsource-433251ade028