การใช้ไลบรารีเพจจิ้งในแอปควรจับคู่กับกลยุทธ์การทดสอบที่มีประสิทธิภาพ คุณควรทดสอบคอมโพเนนต์การโหลดข้อมูล เช่น PagingSource และ RemoteMediator เพื่อให้แน่ใจว่าคอมโพเนนต์ทำงานตามที่คาดไว้ นอกจากนี้ คุณควรเขียนการทดสอบตั้งแต่ต้นจนจบเพื่อยืนยันว่าคอมโพเนนต์ทั้งหมดในการติดตั้งใช้งานเพจจิ้งทำงานร่วมกันอย่างถูกต้องโดยไม่มีผลข้างเคียงที่ไม่คาดคิด
คู่มือนี้อธิบายวิธีทดสอบไลบรารีเพจจิ้งในเลเยอร์สถาปัตยกรรมต่างๆ ของแอป รวมถึงวิธีเขียนการทดสอบตั้งแต่ต้นจนจบสำหรับการติดตั้งใช้งานเพจจิ้งทั้งหมด
การทดสอบเลเยอร์ UI
เนื่องจาก Compose ใช้ข้อมูลเพจจิ้งแบบประกาศผ่าน collectAsLazyPagingItems การทดสอบเลเยอร์ UI จึงมุ่งเน้นไปที่ Flow<PagingData<Value>> ที่ ViewModel แสดงเป็นหลักได้ หากต้องการเขียนการทดสอบเพื่อยืนยันว่าข้อมูลใน UI เป็นไปตามที่คุณคาดหวัง ให้รวมทรัพยากร Dependency paging-testing โดยมีส่วนขยาย asSnapshot ใน Flow<PagingData<Value>> โดยมี API ใน Lambda Receiver ที่ช่วยให้จำลองการโต้ตอบการเลื่อนได้ โดยจะแสดงผล List<Value> มาตรฐานที่สร้างขึ้นโดยการโต้ตอบการเลื่อนที่จำลองขึ้น
ซึ่งจะช่วยให้คุณยืนยันได้ว่าข้อมูลที่แบ่งหน้ามีองค์ประกอบที่คาดไว้ซึ่งสร้างขึ้นจากการโต้ตอบเหล่านั้น ซึ่งแสดงอยู่ในข้อมูลโค้ดต่อไปนี้
fun test_items_contain_one_to_ten() = runTest {
// Get the Flow of PagingData from the ViewModel under test
val items: Flow<PagingData<String>> = viewModel.items
val itemsSnapshot: List<String> = items.asSnapshot {
// Scroll to the 50th item in the list. This will also suspend till
// the prefetch requirement is met if there's one.
// It also suspends until all loading is complete.
scrollTo(index = 50)
}
// With the asSnapshot complete, you can now verify that the snapshot
// has the expected values
assertEquals(
expected = (0..50).map(Int::toString),
actual = itemsSnapshot
)
}
หรือจะเลื่อนจนกว่าจะพบเพรดิเคตที่กำหนดก็ได้ ดังที่แสดงในข้อมูลโค้ดด้านล่าง
fun test_footer_is_visible() = runTest {
// Get the Flow of PagingData from the ViewModel under test
val items: Flow<PagingData<String>> = viewModel.items
val itemsSnapshot: List<String> = items.asSnapshot {
// Scroll till the footer is visible
appendScrollWhile { item: String -> item != "Footer" }
}
การทดสอบการเปลี่ยนรูปแบบ
นอกจากนี้ คุณควรเขียน Unit Test ที่ครอบคลุมการเปลี่ยนรูปแบบที่ใช้กับสตรีม PagingData ด้วย ใช้ส่วนขยาย asPagingSourceFactory ส่วนขยายนี้ใช้ได้กับข้อมูลประเภทต่อไปนี้
List<Value>Flow<List<Value>>
การเลือกส่วนขยายที่จะใช้ขึ้นอยู่กับสิ่งที่คุณพยายามทดสอบ ใช้:
List<Value>.asPagingSourceFactory(): หากต้องการทดสอบการเปลี่ยนรูปแบบแบบคงที่ เช่นmap()และinsertSeparators()ในข้อมูลFlow<List<Value>>.asPagingSourceFactory(): หากต้องการทดสอบว่าการอัปเดตข้อมูล เช่น การเขียนไปยังแหล่งข้อมูลสำรอง ส่งผลต่อไปป์ไลน์การแบ่งหน้าอย่างไร
หากต้องการใช้ส่วนขยายเหล่านี้ ให้ทำตามรูปแบบต่อไปนี้
- สร้าง
PagingSourceFactoryโดยใช้ส่วนขยายที่เหมาะสมกับความต้องการของคุณ - ใช้
PagingSourceFactoryที่แสดงผลใน fake สำหรับRepository - ส่ง
Repositoryนั้นให้ViewModelของคุณ
จากนั้นคุณจะทดสอบ ViewModel ได้ตามที่อธิบายไว้ในส่วนก่อนหน้า
ลองพิจารณาViewModelต่อไปนี้
class MyViewModel(
myRepository: myRepository
) {
val items = Pager(
config: PagingConfig,
initialKey = null,
pagingSourceFactory = { myRepository.pagingSource() }
)
.flow
.map { pagingData ->
pagingData.insertSeparators<String, String> { before, _ ->
when {
// Add a dashed String separator if the prior item is a multiple of 10
before.last() == '0' -> "---------"
// Return null to avoid adding a separator between two items.
else -> null
}
}
}
หากต้องการทดสอบการเปลี่ยนรูปแบบใน MyViewModel ให้ระบุอินสแตนซ์จำลองของ MyRepository ที่มอบสิทธิ์ให้กับ List แบบคงที่ซึ่งแสดงข้อมูลที่จะเปลี่ยนรูปแบบ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
class FakeMyRepository() : MyRepository {
private val items = (0..100).map(Any::toString)
private val pagingSourceFactory = items.asPagingSourceFactory()
// Expose as a function so a new PagingSource instance is
// created each time it is called by the Pager
fun pagingSource() = pagingSourceFactory()
}
จากนั้นคุณสามารถเขียนการทดสอบสำหรับตรรกะตัวคั่นได้ดังในข้อมูลโค้ดต่อไปนี้
fun test_separators_are_added_every_10_items() = runTest {
// Create your ViewModel
val viewModel = MyViewModel(
myRepository = FakeMyRepository()
)
// Get the Flow of PagingData from the ViewModel with the separator transformations applied
val items: Flow<PagingData<String>> = viewModel.items
val snapshot: List<String> = items.asSnapshot()
// With the asSnapshot complete, you can now verify that the snapshot
// has the expected separators.
}
ทดสอบสถานะการโหลดของ Compose
หากต้องการทดสอบว่าคอมโพเนนต์ UI ของ Compose ตอบสนองต่อสถานะการโหลดที่แตกต่างกัน (เช่น LoadState.Loading หรือ LoadState.Error) อย่างไรโดยแยกกัน ให้ส่งสแนปชอต PagingData แบบคงที่พร้อม LoadStates ที่ชัดเจนโดยใช้ PagingData.empty และ PagingData.from ผ่าน MutableStateFlow ดังนี้
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun userListScreen_displaysLoadingDataAndErrorStates() {
val idle = LoadState.NotLoading(endOfPaginationReached = false)
// Emit static PagingData snapshots to test UI state transitions without a
// Pager or ViewModel.
val pagingDataFlow = MutableStateFlow(PagingData.empty<User>())
composeTestRule.setContent {
val lazyPagingItems = pagingDataFlow.collectAsLazyPagingItems()
UserListScreen(lazyPagingItems = lazyPagingItems)
}
// Verify the initial loading UI before any items arrive.
pagingDataFlow.value = PagingData.empty(
sourceLoadStates = LoadStates(
refresh = LoadState.Loading,
prepend = idle,
append = idle
)
)
composeTestRule.onNodeWithTag("LoadingSpinner").assertIsDisplayed()
// Verify that loaded items replace the loading indicator.
pagingDataFlow.value = PagingData.from(
data = listOf(User(id = "1", label = "User A")),
sourceLoadStates = LoadStates(
refresh = idle,
prepend = idle,
append = idle
)
)
composeTestRule.onNodeWithTag("LoadingSpinner").assertIsNotDisplayed()
composeTestRule.onNodeWithText("User A").assertIsDisplayed()
// Verify that a refresh failure surfaces the error message in the UI.
pagingDataFlow.value = PagingData.empty(
sourceLoadStates = LoadStates(
refresh = LoadState.Error(IOException("Network unavailable")),
prepend = idle,
append = idle
)
)
composeTestRule.onNodeWithText("Network unavailable").assertIsDisplayed()
}
การทดสอบชั้นข้อมูล
เขียน Unit Test สำหรับคอมโพเนนต์ในชั้นข้อมูลเพื่อให้แน่ใจว่าคอมโพเนนต์เหล่านั้นโหลดข้อมูลจากแหล่งข้อมูลอย่างเหมาะสม ระบุทรัพยากร Dependency เวอร์ชันจำลองเพื่อยืนยันว่าคอมโพเนนต์ที่ทดสอบทำงานได้อย่างถูกต้องโดยแยกต่างหาก คอมโพเนนต์หลักที่คุณต้องทดสอบในเลเยอร์ที่เก็บคือ PagingSource และ RemoteMediator
การทดสอบ PagingSource รายการ
การทำ Unit Test สำหรับการติดตั้งใช้งาน PagingSource เกี่ยวข้องกับการตั้งค่าอินสแตนซ์ PagingSource และการโหลดข้อมูลจากอินสแตนซ์ดังกล่าวด้วย TestPager
หากต้องการตั้งค่าอินสแตนซ์ PagingSource สำหรับการทดสอบ ให้ระบุข้อมูลปลอมให้กับตัวสร้าง ซึ่งจะช่วยให้คุณควบคุมข้อมูลในการทดสอบได้ ในตัวอย่างต่อไปนี้ พารามิเตอร์ RedditApi คืออินเทอร์เฟซ Retrofit ที่กำหนดคำขอของเซิร์ฟเวอร์และคลาสการตอบกลับ เวอร์ชันจำลองสามารถใช้อินเทอร์เฟซ ลบล้างฟังก์ชันที่จำเป็น และระบุเมธอดที่สะดวกเพื่อกำหนดค่าวิธีที่ออบเจ็กต์จำลองควรตอบสนองในการทดสอบ
หลังจากที่สร้างออบเจ็กต์จำลองแล้ว ให้ตั้งค่าทรัพยากร Dependency และเริ่มต้นออบเจ็กต์ PagingSource ในการทดสอบ ตัวอย่างต่อไปนี้แสดงการเริ่มต้นออบเจ็กต์ FakeRedditApi ด้วยรายการโพสต์ทดสอบ และการทดสอบอินสแตนซ์ RedditPagingSource
class SubredditPagingSourceTest {
private val mockPosts = listOf(
postFactory.createRedditPost(DEFAULT_SUBREDDIT),
postFactory.createRedditPost(DEFAULT_SUBREDDIT),
postFactory.createRedditPost(DEFAULT_SUBREDDIT)
)
private val fakeApi = FakeRedditApi().apply {
mockPosts.forEach { post -> addPost(post) }
}
@Test
fun loadReturnsPageWhenOnSuccessfulLoadOfItemKeyedData() = runTest {
val pagingSource = RedditPagingSource(
fakeApi,
DEFAULT_SUBREDDIT
)
val pager = TestPager(CONFIG, pagingSource)
val result = pager.refresh() as LoadResult.Page
// Write assertions against the loaded data
assertThat(result.data)
.containsExactlyElementsIn(mockPosts)
.inOrder()
}
}
TestPager ยังช่วยให้คุณทำสิ่งต่อไปนี้ได้ด้วย
- ทดสอบการโหลดติดต่อกันจาก
PagingSourceโดยทำดังนี้
@Test
fun test_consecutive_loads() = runTest {
val page = with(pager) {
refresh()
append()
append()
} as LoadResult.Page
assertThat(page.data)
.containsExactlyElementsIn(testPosts)
.inOrder()
}
- ทดสอบสถานการณ์ข้อผิดพลาดใน
PagingSourceดังนี้
@Test
fun refresh_returnError() {
val pagingSource = RedditPagingSource(
fakeApi,
DEFAULT_SUBREDDIT
)
// Configure your fake to return errors
fakeApi.setReturnsError()
val pager = TestPager(CONFIG, source)
runTest {
source.errorNextLoad = true
val result = pager.refresh()
assertTrue(result is LoadResult.Error)
val page = pager.getLastLoadedPage()
assertThat(page).isNull()
}
}
การทดสอบ RemoteMediator รายการ
เป้าหมายของ Unit Test RemoteMediator รายการคือการยืนยันว่าฟังก์ชัน load() แสดงผล MediatorResult ที่ถูกต้อง
การทดสอบผลข้างเคียง เช่น การแทรกข้อมูลลงในฐานข้อมูล เหมาะกับการทดสอบการผสานรวมมากกว่า
ขั้นตอนแรกคือการพิจารณาว่าการติดตั้งใช้งาน RemoteMediator ของคุณต้องใช้ทรัพยากรใดบ้าง ตัวอย่างต่อไปนี้แสดงการติดตั้งใช้งาน RemoteMediator ที่ต้องใช้ฐานข้อมูล Room, อินเทอร์เฟซ Retrofit และสตริงการค้นหา
@OptIn(ExperimentalPagingApi::class)
class PageKeyedRemoteMediator(
private val db: RedditDb,
private val redditApi: RedditApi,
private val subredditName: String
) : RemoteMediator<Int, RedditPost>() {
...
}
คุณสามารถระบุอินเทอร์เฟซ Retrofit และสตริงการค้นหาได้ตามที่แสดงในส่วนการทดสอบ PagingSource การจัดเตรียมฐานข้อมูล Room เวอร์ชันจำลองนั้นมีความซับซ้อนมาก ดังนั้นการจัดเตรียมการใช้งานในหน่วยความจำของฐานข้อมูลแทนเวอร์ชันจำลองแบบเต็มจึงอาจง่ายกว่า เนื่องจากการสร้างฐานข้อมูล Room ต้องใช้ออบเจ็กต์ Context คุณจึงต้องวางการทดสอบ RemoteMediator นี้ไว้ในไดเรกทอรี androidTest และเรียกใช้ด้วยรันเนอร์ทดสอบ AndroidJUnit4 เพื่อให้มีสิทธิ์เข้าถึงบริบทของแอปพลิเคชันทดสอบ ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบการวัดคุมได้ที่สร้าง Unit Test ที่มีการวัดคุม
กำหนดฟังก์ชันการแยกส่วนเพื่อให้แน่ใจว่าสถานะจะไม่รั่วไหลระหว่างฟังก์ชันการทดสอบ ซึ่งจะช่วยให้ผลลัพธ์มีความสอดคล้องกันระหว่างการทดสอบแต่ละครั้ง
@ExperimentalPagingApi
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class PageKeyedRemoteMediatorTest {
private val postFactory = PostFactory()
private val mockPosts = listOf(
postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT)
)
private val mockApi = mockRedditApi()
private val mockDb = RedditDb.create(
ApplicationProvider.getApplicationContext(),
useInMemory = true
)
@After
fun tearDown() {
mockDb.clearAllTables()
// Clear out failure message to default to the successful response.
mockApi.failureMsg = null
// Clear out posts after each test run.
mockApi.clearPosts()
}
}
ขั้นตอนถัดไปคือการทดสอบฟังก์ชัน load() ในตัวอย่างนี้ มี 3 กรณีที่ต้องทดสอบ ดังนี้
- กรณีแรกคือเมื่อ
mockApiแสดงผลข้อมูลที่ถูกต้อง ฟังก์ชันload()ควรแสดงผลMediatorResult.Successและพร็อพเพอร์ตี้endOfPaginationReachedควรเป็นfalse - กรณีที่ 2 คือเมื่อ
mockApiแสดงการตอบกลับที่สำเร็จ แต่ข้อมูลที่แสดงผลว่างเปล่า ฟังก์ชันload()ควรแสดงผลMediatorResult.Successและพร็อพเพอร์ตี้endOfPaginationReachedควรเป็นtrue - กรณีที่ 3 คือเมื่อ
mockApiแสดงข้อยกเว้นเมื่อดึงข้อมูล ฟังก์ชันload()ควรแสดงผลMediatorResult.Error
ทำตามขั้นตอนต่อไปนี้เพื่อทดสอบกรณีแรก
- ตั้งค่า
mockApiด้วยข้อมูลโพสต์ที่จะแสดงผล - เริ่มต้นออบเจ็กต์
RemoteMediator - ทดสอบฟังก์ชัน
load()
@Test
fun refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() = runTest {
// Add mock results for the API to return.
mockPosts.forEach { post -> mockApi.addPost(post) }
val remoteMediator = PageKeyedRemoteMediator(
mockDb,
mockApi,
SubRedditViewModel.DEFAULT_SUBREDDIT
)
val pagingState = PagingState<Int, RedditPost>(
listOf(),
null,
PagingConfig(10),
10
)
val result = remoteMediator.load(LoadType.REFRESH, pagingState)
assertTrue { result is MediatorResult.Success }
assertFalse { (result as MediatorResult.Success).endOfPaginationReached }
}
การทดสอบที่ 2 กำหนดให้ mockApi แสดงผลลัพธ์ที่ว่างเปล่า เนื่องจากคุณล้างข้อมูลจาก mockApi หลังจากการทดสอบแต่ละครั้ง ระบบจะแสดงผลลัพธ์ที่ว่างเปล่าโดยค่าเริ่มต้น
@Test
fun refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() = runTest {
// To test endOfPaginationReached, don't set up the mockApi to return post
// data here.
val remoteMediator = PageKeyedRemoteMediator(
mockDb,
mockApi,
SubRedditViewModel.DEFAULT_SUBREDDIT
)
val pagingState = PagingState<Int, RedditPost>(
listOf(),
null,
PagingConfig(10),
10
)
val result = remoteMediator.load(LoadType.REFRESH, pagingState)
assertTrue { result is MediatorResult.Success }
assertTrue { (result as MediatorResult.Success).endOfPaginationReached }
}
การทดสอบขั้นสุดท้ายกำหนดให้ mockApi แสดงข้อยกเว้นเพื่อให้การทดสอบสามารถยืนยันได้ว่าฟังก์ชัน load() แสดงผล MediatorResult.Error อย่างถูกต้อง
@Test
fun refreshLoadReturnsErrorResultWhenErrorOccurs() = runTest {
// Set up failure message to throw exception from the mock API.
mockApi.failureMsg = "Throw test failure"
val remoteMediator = PageKeyedRemoteMediator(
mockDb,
mockApi,
SubRedditViewModel.DEFAULT_SUBREDDIT
)
val pagingState = PagingState<Int, RedditPost>(
listOf(),
null,
PagingConfig(10),
10
)
val result = remoteMediator.load(LoadType.REFRESH, pagingState)
assertTrue {result is MediatorResult.Error }
}
การทดสอบตั้งแต่ต้นจนจบ
การทำ Unit Test ช่วยให้มั่นใจได้ว่าคอมโพเนนต์เพจจิ้งแต่ละรายการทำงานแยกกัน แต่การทดสอบตั้งแต่ต้นจนจบจะช่วยให้มั่นใจได้มากขึ้นว่าแอปพลิเคชันทำงานโดยรวมได้ การทดสอบเหล่านี้ช่วยยืนยันว่าชั้นข้อมูล (PagingSource หรือ RemoteMediator), ViewModel และ Compose UI ผสานรวมกันได้อย่างราบรื่นโดยไม่มีผลข้างเคียงที่ไม่คาดคิด การทดสอบจะยังคงต้องใช้ทรัพยากร Dependency จำลองบางอย่าง แต่โดยทั่วไปแล้วจะครอบคลุมโค้ดแอปส่วนใหญ่
ตัวอย่างในส่วนนี้ใช้ทรัพยากร Dependency ของ API จำลองเพื่อหลีกเลี่ยงการใช้เครือข่ายในการทดสอบ API จำลองได้รับการกำหนดค่าให้แสดงผลชุดข้อมูลทดสอบที่สอดคล้องกัน ซึ่งส่งผลให้ทำการทดสอบซ้ำได้ สำหรับการทดสอบตั้งแต่ต้นจนจบ โดยทั่วไปคุณจะสลับ API เครือข่ายจริงเป็น API ปลอม แต่คุณยังคงอนุญาตให้ไลบรารี Paging จัดการการดึงข้อมูลจริงและการแคชฐานข้อมูลในเครื่อง (หากใช้ RemoteMediator) เพื่อรักษาความถูกต้องของการทดสอบ
เขียนโค้ดในลักษณะที่ช่วยให้คุณสลับใช้ทรัพยากร Dependency เวอร์ชันจำลองได้อย่างง่ายดาย ตัวอย่างต่อไปนี้ใช้การติดตั้งใช้งาน Service Locator พื้นฐานและตั้งค่าการทดสอบด้วย API จำลองเพื่อยืนยันว่าหน้าจอ Compose ใช้และแสดงข้อมูลที่มีการแบ่งหน้าอย่างถูกต้อง ในแอปขนาดใหญ่ การใช้ไลบรารีการแทรกทรัพยากร Dependency เช่น Hilt จะช่วยจัดการกราฟทรัพยากร Dependency ที่ซับซ้อนมากขึ้นได้
หลังจากตั้งค่าโครงสร้างการทดสอบแล้ว ขั้นตอนถัดไปคือการยืนยันว่าข้อมูลที่ได้จากการติดตั้งใช้งาน Pager นั้นถูกต้อง การทดสอบหนึ่งควรยืนยันว่า Compose UI แสดงรายการที่ถูกต้องเมื่อหน้าจอโหลดครั้งแรก และการทดสอบอีกรายการควรยืนยันว่า UI โหลดข้อมูลเพิ่มเติมอย่างถูกต้องตามการโต้ตอบของผู้ใช้
ในตัวอย่างต่อไปนี้ การทดสอบจะยืนยันว่า UI แสดงข้อมูลที่มีการแบ่งหน้าตามที่คาดไว้
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertDoesNotExist
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RedditScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
private val postFactory = PostFactory()
private val mockApi = MockRedditApi()
@Before
fun setup() {
// Pre-populate the mock API with test data for the default subreddit
mockApi.addPost(postFactory.createRedditPost(subreddit = "androiddev", title = "Jetpack Compose Paging"))
// Swap your real dependency injection module/Service Locator with the mock API
ServiceLocator.swap(
object : DefaultServiceLocator(useInMemoryDb = true) {
override fun getRedditApi(): RedditApi = mockApi
}
)
}
@Test
fun loadsTheDefaultResults() = runTest {
// 1. Set the Compose UI content
composeTestRule.setContent {
MyTheme {
// Assume that this composable uses `collectAsLazyPagingItems()` internally
RedditScreen(initialSubreddit = "androiddev")
}
}
// 2. Wait for the asynchronous Paging loads to complete
composeTestRule.waitUntilExactlyOneExists(
matcher = hasText("Jetpack Compose Paging"),
timeoutMillis = 5000
)
// 3. Assert that the loaded paged items are displayed correctly on screen
composeTestRule.onNodeWithText("Jetpack Compose Paging").assertIsDisplayed()
}
@Test
fun loadsNewDataBasedOnUserInput() = runTest {
// Add data for a different subreddit to the mock API
mockApi.addPost(postFactory.createRedditPost(subreddit = "compose", title = "Compose Testing"))
composeTestRule.setContent {
MyTheme {
RedditScreen(initialSubreddit = "androiddev")
}
}
// Wait for the initial load to finish
composeTestRule.waitUntilExactlyOneExists(hasText("Jetpack Compose Paging"))
// Simulate user entering a new subreddit in a text field and clicking search
composeTestRule.onNodeWithTag("SubredditInput").performTextClearance()
composeTestRule.onNodeWithTag("SubredditInput").performTextInput("compose")
composeTestRule.onNodeWithTag("SearchButton").performClick()
// Wait for the new paged data to load
composeTestRule.waitUntilExactlyOneExists(
matcher = hasText("Compose Testing"),
timeoutMillis = 5000
)
// Assert the old data is gone and the new data is displayed
composeTestRule.onNodeWithText("Jetpack Compose Paging").assertDoesNotExist()
composeTestRule.onNodeWithText("Compose Testing").assertIsDisplayed()
}
}
เนื่องจาก Flow<PagingData> โหลดข้อมูลแบบอะซิงโครนัส คุณจึงต้องให้เวลาไลบรารี Paging ในการดึงข้อมูลการโหลดครั้งแรกและส่งไปยัง collectAsLazyPagingItems ก่อนที่จะทำการยืนยัน โดยใช้ composeTestRule.waitUntil หรือ waitUntilExactlyOneExists ดังที่แสดงในตัวอย่างก่อนหน้า
หลังจากโหลดข้อมูลแล้ว คุณสามารถยืนยันกับแผนผังความหมายของ Compose โดยตรงโดยใช้ onNodeWithText เพื่อยืนยันว่าระบบแสดงผลรายการใน LazyColumn จริงๆ
แหล่งข้อมูลเพิ่มเติม
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- หน้าเว็บจากเครือข่ายและฐานข้อมูล
- ย้ายข้อมูลไปยัง Paging 3
- โหลดและแสดงข้อมูลแบบแบ่งหน้า