如何为Android Paging Library编写分页网络请求单元测试
分页网络请求单元测试方案
针对你基于Android Paging Library实现的分页逻辑,我们可以分数据源类(ListDataSource)、仓库类(ListPagingRepository)、ViewModel方法三个层级编写单元测试,以下是具体实现步骤:
一、依赖准备
确保测试依赖中包含:
kotlinx-coroutines-test:处理协程测试场景mockk(或Mockito):模拟NetworkService等依赖实例androidx.paging:paging-testing:Paging库专属测试工具
二、测试ListDataSource
数据源是分页逻辑核心,重点测试load方法的不同场景:成功返回满页数据、返回不足页数据、网络异常。
示例代码(MockK+协程测试)
import app.cash.turbine.test import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class ListDataSourceTest { private val mockNetworkService = mockk<NetworkService>() private val testSearchKey = "test" private val dataSource = ListDataSource(mockNetworkService, testSearchKey) @Test fun `load first page returns valid Page with next key`() = runTest { // 模拟网络返回30条满页数据 val mockResponse = mockk<ApiResponse> { every { response } returns mockk { every { list } returns List(30) { ListItem(id = it) } } } coEvery { mockNetworkService.getList(testSearchKey, 0, 30) } returns mockResponse val result = dataSource.load(LoadParams.Refresh(null, 30, false)) // 验证返回结果符合预期 assertTrue(result is LoadResult.Page) assertEquals(30, (result as LoadResult.Page).data.size) assertEquals(null, result.prevKey) assertEquals(1, result.nextKey) } @Test fun `load page with less than 30 items returns Page with null next key`() = runTest { // 模拟返回20条不足页数据 val mockResponse = mockk<ApiResponse> { every { response } returns mockk { every { list } returns List(20) { ListItem(id = it) } } } coEvery { mockNetworkService.getList(testSearchKey, 1, 30) } returns mockResponse val result = dataSource.load(LoadParams.Append(1, 30, false)) assertTrue(result is LoadResult.Page) assertEquals(20, (result as LoadResult.Page).data.size) assertEquals(0, result.prevKey) assertEquals(null, result.nextKey) } @Test fun `load throws exception returns Error result`() = runTest { // 模拟网络异常 val testException = RuntimeException("Network error") coEvery { mockNetworkService.getList(testSearchKey, any(), any()) } throws testException val result = dataSource.load(LoadParams.Refresh(null, 30, false)) assertTrue(result is LoadResult.Error) assertEquals(testException, (result as LoadResult.Error).throwable) } }
三、测试ListPagingRepository
仓库类主要测试Pager的初始化逻辑,以及数据源创建是否正常。
示例代码
import io.mockk.mockk import org.junit.Test import kotlin.test.assertTrue import kotlin.test.assertFalse class ListPagingRepositoryTest { private val mockNetworkService = mockk<NetworkService>() private val repository = ListPagingRepository(mockNetworkService) @Test fun `createSource and createPager initializes pager correctly`() { assertFalse(repository.isPagerInitialized()) repository.createSource("test") repository.createPager() assertTrue(repository.isPagerInitialized()) // 验证Pager配置符合预期 val pager = repository.getListPager() assertEquals(15, pager.config.pageSize) assertEquals(false, pager.config.enablePlaceholders) } }
四、测试ViewModel的getPaginatedList方法
重点测试Flow是否正确发射数据,以及数据映射逻辑是否正常。
示例代码
import app.cash.turbine.test import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.test.assertEquals class ListViewModelTest { private val mockRepository = mockk<ListPagingRepository>(relaxed = true) private val mockMapper = mockk<ListMapper>(relaxed = true) private val viewModel = ListViewModel(mockRepository, mockMapper) @Test fun `getPaginatedList returns mapped PagingData flow when pager is initialized`() = runTest { // 模拟仓库返回的PagingData Flow val testPagingData = PagingData.from(listOf(ListItem(id = 1))) val mockPager = mockk<Pager<Int, ListItem>> { every { flow } returns kotlinx.coroutines.flow.flow { emit(testPagingData) } } coEvery { mockRepository.isPagerInitialized() } returns true coEvery { mockRepository.getListPager() } returns mockPager // 模拟数据映射结果 val mappedItem = MappedListItem(id = 1) coEvery { mockMapper.map(any<ListItem>()) } returns mappedItem viewModel.getPaginatedList("test").test { val emittedData = awaitItem() // 转换为列表验证映射结果 val items = mutableListOf<MappedListItem>() emittedData.collect { items.add(it) } assertEquals(1, items.size) assertEquals(mappedItem.id, items[0].id) awaitComplete() } } @Test fun `getPaginatedList returns empty flow when pager is not initialized`() = runTest { coEvery { mockRepository.isPagerInitialized() } returns false viewModel.getPaginatedList("test").test { expectNoEvents() } } }
关键测试要点
- 协程处理:所有涉及
suspend的测试用runTest包裹,简化协程测试流程 - 边界场景覆盖:测试第一页、最后一页(无下一页)、网络异常、空数据等情况
- 数据映射验证:确保ViewModel中的
map操作正确转换数据 - Pager配置验证:确认仓库创建的Pager使用了预期的PagingConfig
内容的提问来源于stack exchange,提问作者Jona
相关产品推荐
相关产品推荐

