Menerapkan library Paging di aplikasi Anda harus dipasangkan dengan strategi
pengujian yang kuat. Anda harus menguji komponen pemuatan data, seperti
PagingSource
dan
RemoteMediator
untuk memastikan keduanya berfungsi seperti yang diharapkan. Anda juga harus menulis pengujian menyeluruh untuk
memverifikasi bahwa semua komponen dalam implementasi Paging berfungsi dengan benar
bersama-sama tanpa efek samping yang tidak terduga.
Panduan ini menjelaskan cara menguji library Paging di lapisan arsitektur yang berbeda pada aplikasi Anda, serta cara menulis pengujian menyeluruh untuk seluruh implementasi Paging.
Pengujian lapisan UI
Data yang diambil dengan library Paging digunakan di UI sebagai
Flow<PagingData<Value>>
.
Untuk menulis pengujian guna memverifikasi data di UI seperti yang Anda harapkan, sertakan
dependensi paging-testing
.
Dependensi ini berisi ekstensi asSnapshot()
pada Flow<PagingData<Value>>
. Dependensi ini
menawarkan API di penerima lambda yang memungkinkan interaksi
scroll tiruan. Dependensi ini menampilkan List<Value>
standar yang dihasilkan oleh interaksi scroll tiruan, yang memungkinkan Anda menyatakan bahwa data yang di-page berisi elemen yang diharapkan dari hasil interaksi tersebut.
Hal ini diilustrasikan dalam cuplikan berikut:
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
)
}
Atau, Anda dapat men-scroll hingga predikat tertentu terpenuhi seperti yang terlihat dalam cuplikan di bawah:
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" }
}
Menguji transformasi
Anda juga harus menulis pengujian unit yang mencakup transformasi apa pun yang diterapkan ke
aliran PagingData
. Gunakan ekstensi
asPagingSourceFactory
. Ekstensi ini tersedia di jenis data berikut:
List<Value>
.Flow<List<Value>>
.
Pilihan ekstensi yang akan digunakan bergantung pada apa yang Anda coba uji. Gunakan:
List<Value>.asPagingSourceFactory()
: Jika Anda ingin menguji transformasi statis sepertimap()
daninsertSeparators()
pada data.Flow<List<Value>>.asPagingSourceFactory()
: Jika Anda ingin menguji pengaruh update pada data, seperti menulis ke sumber data pendukung atau pengaruhnya terhadap pipeline paging.
Untuk menggunakan salah satu ekstensi ini, ikuti pola berikut:
- Buat
PagingSourceFactory
menggunakan ekstensi yang sesuai dengan kebutuhan Anda. - Gunakan
PagingSourceFactory
yang ditampilkan dalam palsu untukRepository
. - Teruskan
Repository
tersebut keViewModel
Anda.
ViewModel
kemudian dapat diuji seperti yang dibahas di bagian sebelumnya.
Pertimbangkan ViewModel
berikut:
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
}
}
}
Untuk menguji transformasi di MyViewModel
, sediakan instance
MyRepository
palsu yang didelegasikan ke List
untuk mewakili data yang akan diubah
seperti yang ditunjukkan dalam cuplikan berikut:
class FakeMyRepository(): MyRepository {
private val items = (0..100).map(Any::toString)
private val pagingSourceFactory = items.asPagingSourceFactory()
val pagingSource = pagingSourceFactory()
}
Kemudian, Anda dapat menulis pengujian untuk logika pemisah seperti dalam cuplikan berikut:
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.
}
Pengujian lapisan data
Tulis pengujian unit untuk komponen dalam lapisan data Anda guna memastikan bahwa unit
memuat data dari sumber data Anda dengan tepat. Berikan
versi palsu
dependensi untuk memverifikasi bahwa komponen yang sedang diuji berfungsi dengan benar secara
terpisah. Komponen utama yang perlu Anda uji di lapisan repositori adalah
PagingSource
dan RemoteMediator
. Contoh di bagian berikut ini akan didasarkan pada
Contoh Paging dengan
Jaringan.
Pengujian PagingSource
Pengujian unit untuk implementasi PagingSource
melibatkan penyiapan
instance PagingSource
dan memuat data darinya dengan TestPager
.
Untuk menyiapkan instance PagingSource
guna pengujian, berikan data palsu ke
konstruktor. Hal ini memberi Anda kontrol atas data dalam pengujian.
Dalam contoh berikut, RedditApi
adalah Retrofit
yang mendefinisikan permintaan server dan kelas respons.
Versi palsu dapat menerapkan antarmuka, mengganti fungsi yang diperlukan,
dan menyediakan metode yang mudah untuk mengonfigurasi reaksi objek palsu
dalam pengujian.
Setelah kode palsu diterapkan, siapkan dependensi dan inisialisasi objek PagingSource
dalam pengujian. Contoh berikut menunjukkan
inisialisasi objek FakeRedditApi
dengan daftar postingan pengujian, dan pengujian
instance 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
juga memungkinkan Anda melakukan hal berikut:
- Uji muatan berturut-turut dari
PagingSource
:
@Test
fun test_consecutive_loads() = runTest {
val page = with(pager) {
refresh()
append()
append()
} as LoadResult.Page
assertThat(page.data)
.containsExactlyElementsIn(testPosts)
.inOrder()
}
- Uji skenario error di
PagingSource
Anda:
@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()
}
}
Pengujian RemoteMediator
Sasaran pengujian unit RemoteMediator
adalah untuk memverifikasi bahwa fungsi load()
menampilkan MediatorResult
yang benar.
Pengujian efek samping, seperti data yang dimasukkan ke dalam database,
lebih cocok untuk pengujian integrasi.
Langkah pertama adalah menentukan dependensi yang diperlukan
implementasi RemoteMediator
Anda. Contoh berikut menunjukkan implementasi RemoteMediator
yang memerlukan database Room, antarmuka Retrofit, dan string
penelusuran:
Kotlin
@OptIn(ExperimentalPagingApi::class) class PageKeyedRemoteMediator( private val db: RedditDb, private val redditApi: RedditApi, private val subredditName: String ) : RemoteMediator<Int, RedditPost>() { ... }
Java
public class PageKeyedRemoteMediator extends RxRemoteMediator<Integer, RedditPost> { @NonNull private RedditDb db; @NonNull private RedditPostDao postDao; @NonNull private SubredditRemoteKeyDao remoteKeyDao; @NonNull private RedditApi redditApi; @NonNull private String subredditName; public PageKeyedRemoteMediator( @NonNull RedditDb db, @NonNull RedditApi redditApi, @NonNull String subredditName ) { this.db = db; this.postDao = db.posts(); this.remoteKeyDao = db.remoteKeys(); this.redditApi = redditApi; this.subredditName = subredditName; ... } }
Java
public class PageKeyedRemoteMediator extends ListenableFutureRemoteMediator<Integer, RedditPost> { @NonNull private RedditDb db; @NonNull private RedditPostDao postDao; @NonNull private SubredditRemoteKeyDao remoteKeyDao; @NonNull private RedditApi redditApi; @NonNull private String subredditName; @NonNull private Executor bgExecutor; public PageKeyedRemoteMediator( @NonNull RedditDb db, @NonNull RedditApi redditApi, @NonNull String subredditName, @NonNull Executor bgExecutor ) { this.db = db; this.postDao = db.posts(); this.remoteKeyDao = db.remoteKeys(); this.redditApi = redditApi; this.subredditName = subredditName; this.bgExecutor = bgExecutor; ... } }
Anda dapat memberikan antarmuka Retrofit dan string penelusuran seperti yang ditunjukkan di
bagian pengujian PagingSource. Menyediakan versi tiruan
database Room sangat diperlukan, sehingga dapat lebih mudah untuk menyediakan
implementasi dalam memori
dari database daripada versi tiruan lengkap. Karena membuat database Room
memerlukan objek Context
, Anda harus
menempatkan pengujian RemoteMediator
ini di direktori androidTest
dan menjalankannya
dengan runner pengujian AndroidJUnit4 sehingga memiliki akses ke konteks aplikasi
pengujian. Untuk informasi selengkapnya tentang pengujian berinstrumen, lihat Membuat pengujian unit berinstrumen.
Tentukan fungsi penghapusan untuk memastikan bahwa status tidak bocor di antara fungsi pengujian. Hal ini untuk memastikan hasil yang konsisten di antara pengujian.
Kotlin
@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() } }
Java
@RunWith(AndroidJUnit4.class) public class PageKeyedRemoteMediatorTest { static PostFactory postFactory = new PostFactory(); static List<RedditPost> mockPosts = new ArrayList<>(); static MockRedditApi mockApi = new MockRedditApi(); private RedditDb mockDb = RedditDb.Companion.create( ApplicationProvider.getApplicationContext(), true ); static { for (int i=0; i<3; i++) { RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT); mockPosts.add(post); } } @After public void tearDown() { mockDb.clearAllTables(); // Clear the failure message after each test run. mockApi.setFailureMsg(null); // Clear out posts after each test run. mockApi.clearPosts(); } }
Java
@RunWith(AndroidJUnit4.class) public class PageKeyedRemoteMediatorTest { static PostFactory postFactory = new PostFactory(); static List<RedditPost> mockPosts = new ArrayList<>(); static MockRedditApi mockApi = new MockRedditApi(); private RedditDb mockDb = RedditDb.Companion.create( ApplicationProvider.getApplicationContext(), true ); static { for (int i=0; i<3; i++) { RedditPost post = postFactory.createRedditPost(DEFAULT_SUBREDDIT); mockPosts.add(post); } } @After public void tearDown() { mockDb.clearAllTables(); // Clear the failure message after each test run. mockApi.setFailureMsg(null); // Clear out posts after each test run. mockApi.clearPosts(); } }
Langkah berikutnya adalah menguji fungsi load()
. Dalam contoh ini, ada tiga
kasus yang harus diuji:
- Kasus pertama adalah saat
mockApi
menampilkan data yang valid. Fungsiload()
akan menampilkanMediatorResult.Success
, dan propertiendOfPaginationReached
harusfalse
. - Kasus kedua adalah saat
mockApi
mengembalikan respons yang berhasil, tetapi data yang ditampilkan kosong. Fungsiload()
harus menampilkanMediatorResult.Success
, dan propertiendOfPaginationReached
harustrue
. - Kasus ketiga adalah saat
mockApi
memunculkan pengecualian sewaktu mengambil data. Fungsiload()
harus menampilkanMediatorResult.Error
.
Ikuti langkah-langkah berikut untuk menguji kasus pertama:
- Siapkan
mockApi
dengan data postingan yang akan ditampilkan. - Inisialisasi objek
RemoteMediator
. - Uji fungsi
load()
Kotlin
@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 } }
Java
@Test public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() throws InterruptedException { // Add mock results for the API to return. for (RedditPost post: mockPosts) { mockApi.addPost(post); } PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success && ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == false); }
Java
@Test public void refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() throws InterruptedException, ExecutionException { // Add mock results for the API to return. for (RedditPost post: mockPosts) { mockApi.addPost(post); } PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class)); assertFalse(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached()); }
Pengujian kedua memerlukan mockApi
untuk menampilkan hasil kosong. Karena Anda
menghapus data dari mockApi
setelah setiap pengujian dijalankan, data akan menampilkan
hasil kosong secara default.
Kotlin
@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 } }
Java
@Test public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() throws InterruptedException() { // To test endOfPaginationReached, don't set up the mockApi to return post // data here. PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Success && ((RemoteMediator.MediatorResult.Success) value).endOfPaginationReached() == true); }
Java
@Test public void refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() throws InterruptedException, ExecutionException { // To test endOfPaginationReached, don't set up the mockApi to return post // data here. PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Success.class)); assertTrue(((RemoteMediator.MediatorResult.Success) result).endOfPaginationReached()); }
Pengujian terakhir memerlukan mockApi
untuk menampilkan pengecualian sehingga pengujian dapat
memverifikasi bahwa fungsi load()
menampilkan MediatorResult.Error
dengan benar.
Kotlin
@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 } }
Java
@Test public void refreshLoadReturnsErrorResultWhenErrorOccurs() throws InterruptedException { // Set up failure message to throw exception from the mock API. mockApi.setFailureMsg("Throw test failure"); PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); remoteMediator.loadSingle(LoadType.REFRESH, pagingState) .test() .await() .assertValueCount(1) .assertValue(value -> value instanceof RemoteMediator.MediatorResult.Error); }
Java
@Test public void refreshLoadReturnsErrorResultWhenErrorOccurs() throws InterruptedException, ExecutionException { // Set up failure message to throw exception from the mock API. mockApi.setFailureMsg("Throw test failure"); PageKeyedRemoteMediator remoteMediator = new PageKeyedRemoteMediator( mockDb, mockApi, SubRedditViewModel.DEFAULT_SUBREDDIT, new CurrentThreadExecutor() ); PagingState<Integer, RedditPost> pagingState = new PagingState<>( new ArrayList(), null, new PagingConfig(10), 10 ); RemoteMediator.MediatorResult result = remoteMediator.loadFuture(LoadType.REFRESH, pagingState).get(); assertThat(result, instanceOf(RemoteMediator.MediatorResult.Error.class)); }
Pengujian menyeluruh
Pengujian unit memberikan jaminan bahwa setiap komponen Paging berfungsi secara terpisah, tetapi pengujian menyeluruh memberikan keyakinan lebih bahwa aplikasi berfungsi secara keseluruhan. Pengujian ini masih memerlukan beberapa dependensi tiruan, tetapi umumnya mencakup sebagian besar kode aplikasi Anda.
Contoh di bagian ini menggunakan dependensi tiruan API untuk menghindari penggunaan jaringan dalam pengujian. API tiruan dikonfigurasi untuk mengembalikan kumpulan data pengujian yang konsisten, sehingga menghasilkan pengujian berulang. Tentukan dependensi yang akan ditukar dengan implementasi tiruan berdasarkan hal yang dilakukan setiap dependensi, seberapa konsisten outputnya, dan seberapa banyak fidelitas yang Anda butuhkan dari pengujian.
Tulis kode Anda dengan cara yang memudahkan Anda menukar versi tiruan dependensi Anda. Contoh berikut menggunakan implementasi pencari lokasi layanan dasar untuk menyediakan dan mengubah dependensi sesuai kebutuhan. Pada aplikasi yang lebih besar, penggunaan library injeksi dependensi seperti Hilt dapat membantu mengelola grafik dependensi yang lebih kompleks.
Kotlin
class RedditActivityTest { companion object { private const val TEST_SUBREDDIT = "test" } private val postFactory = PostFactory() private val mockApi = MockRedditApi().apply { addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)) addPost(postFactory.createRedditPost(TEST_SUBREDDIT)) addPost(postFactory.createRedditPost(TEST_SUBREDDIT)) } @Before fun init() { val app = ApplicationProvider.getApplicationContext<Application>() // Use a controlled service locator with a mock API. ServiceLocator.swap( object : DefaultServiceLocator(app = app, useInMemoryDb = true) { override fun getRedditApi(): RedditApi = mockApi } ) } }
Java
public class RedditActivityTest { public static final String TEST_SUBREDDIT = "test"; private static PostFactory postFactory = new PostFactory(); private static MockRedditApi mockApi = new MockRedditApi(); static { mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); } @Before public void setup() { Application app = ApplicationProvider.getApplicationContext(); // Use a controlled service locator with a mock API. ServiceLocator.Companion.swap( new DefaultServiceLocator(app, true) { @NotNull @Override public RedditApi getRedditApi() { return mockApi; } } ); } }
Java
public class RedditActivityTest { public static final String TEST_SUBREDDIT = "test"; private static PostFactory postFactory = new PostFactory(); private static MockRedditApi mockApi = new MockRedditApi(); static { mockApi.addPost(postFactory.createRedditPost(DEFAULT_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); mockApi.addPost(postFactory.createRedditPost(TEST_SUBREDDIT)); } @Before public void setup() { Application app = ApplicationProvider.getApplicationContext(); // Use a controlled service locator with a mock API. ServiceLocator.Companion.swap( new DefaultServiceLocator(app, true) { @NotNull @Override public RedditApi getRedditApi() { return mockApi; } } ); } }
Setelah Anda menyiapkan struktur pengujian, langkah berikutnya adalah memverifikasi bahwa data
yang ditampilkan oleh implementasi Pager
sudah benar. Satu pengujian harus memastikan bahwa objek Pager
memuat data default saat halaman pertama dimuat, dan pengujian lainnya
harus memverifikasi bahwa objek Pager
memuat data tambahan dengan benar berdasarkan
input pengguna. Pada contoh berikut, pengujian memverifikasi bahwa objek Pager
memperbarui RecyclerView.Adapter
dengan jumlah item
yang benar yang dikembalikan dari API saat pengguna memasukkan subreddit yang berbeda ke penelusuran.
Kotlin
@Test fun loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity::class.java) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView assertEquals(1, recyclerView.adapter?.itemCount) } } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. fun loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity::class.java ) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView // Verify that it loads the default data first. assertEquals(1, recyclerView.adapter?.itemCount) } // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ) onView(withId(R.id.list)).check { view, noViewFoundException -> if (noViewFoundException != null) { throw noViewFoundException } val recyclerView = view as RecyclerView assertEquals(2, recyclerView.adapter?.itemCount) } }
Java
@Test public void loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(1, recyclerView.getAdapter().getItemCount()); }); } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. public void loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; // Verify that it loads the default data first. assertEquals(1, recyclerView.getAdapter().getItemCount()); }); // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(2, recyclerView.getAdapter().getItemCount()); }); }
Java
@Test public void loadsTheDefaultResults() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(1, recyclerView.getAdapter().getItemCount()); }); } @Test // Verify that the default data is swapped out when the user searches for a // different subreddit. public void loadsTheTestResultsWhenSearchingForSubreddit() { ActivityScenario.launch(RedditActivity.class); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; // Verify that it loads the default data first. assertEquals(1, recyclerView.getAdapter().getItemCount()); }); // Search for test subreddit instead of default to trigger new data load. onView(withId(R.id.input)).perform( replaceText(TEST_SUBREDDIT), pressKey(KeyEvent.KEYCODE_ENTER) ); onView(withId(R.id.list)).check((view, noViewFoundException) -> { if (noViewFoundException != null) { throw noViewFoundException; } RecyclerView recyclerView = (RecyclerView) view; assertEquals(2, recyclerView.getAdapter().getItemCount()); }); }
Pengujian berinstrumen harus memverifikasi bahwa data ditampilkan dengan benar di UI. Lakukan
ini dengan memverifikasi bahwa jumlah item yang benar ada di
RecyclerView.Adapter
, atau dengan melakukan iterasi melalui masing-masing tampilan baris dan
memverifikasi bahwa data diformat dengan benar.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Halaman dari jaringan dan database
- Bermigrasi ke Paging 3
- Memuat dan menampilkan data yang dibagi-bagi