Implementację biblioteki stronicowania w aplikacji należy połączyć z niezawodną strategią testowania. Przetestuj komponenty wczytywania danych, takie jak PagingSource
i RemoteMediator
, aby mieć pewność, że działają one zgodnie z oczekiwaniami. Warto też tworzyć kompleksowe testy, aby sprawdzać, czy wszystkie komponenty w implementacji stronicowania działają ze sobą prawidłowo bez nieoczekiwanych skutków ubocznych.
Z tego przewodnika dowiesz się, jak testować bibliotekę stron w różnych warstwach architektury aplikacji, a także jak pisać kompleksowe testy całej implementacji stronicowania.
Testy warstw interfejsu
Dane pobrane za pomocą biblioteki stronicowania są wykorzystywane w interfejsie jako Flow<PagingData<Value>>
.
Aby napisać testy w celu weryfikacji danych w interfejsie, dodaj zależność paging-testing
.
Zawiera rozszerzenie asSnapshot()
na platformie Flow<PagingData<Value>>
. Udostępnia on w swoim odbiorniku lambda interfejsy API, które umożliwiają symulowanie interakcji z przewijaniem. Zwraca standardowy List<Value>
wygenerowany przez naśladowane interakcje z przewijaniem, co pozwala stwierdzić, że dane pozyskane na stronie zawierają oczekiwane elementy wygenerowane przez te interakcje.
Przedstawiliśmy to na przykładzie tego fragmentu kodu:
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
)
}
Możesz też przewijać, aż zostanie osiągnięty określony predykat, jak pokazano we fragmencie poniżej:
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" }
}
Testowanie przekształceń
Utwórz też testy jednostkowe obejmujące wszystkie przekształcenia zastosowane do strumienia PagingData
. Użyj rozszerzenia asPagingSourceFactory
. To rozszerzenie jest dostępne w przypadku tych typów danych:
List<Value>
.Flow<List<Value>>
.
Wybór rozszerzenia zależy od tego, co chcesz przetestować. Użyj opcji:
List<Value>.asPagingSourceFactory()
: jeśli chcesz przetestować na danych przekształcenia statyczne, takie jakmap()
iinsertSeparators()
.Flow<List<Value>>.asPagingSourceFactory()
: jeśli chcesz przetestować, jak aktualizacje danych, takie jak zapisywanie w źródłowym źródle danych, wpływają na potok stronicowania.
Aby korzystać z jednego z tych rozszerzeń, postępuj zgodnie z tym wzorcem:
- Utwórz
PagingSourceFactory
, używając odpowiedniego rozszerzenia. - Użyj zwróconego
PagingSourceFactory
jako fałszywego elementuRepository
. - Przekaż ten
Repository
na urządzenieViewModel
.
Następnie można przetestować ViewModel
zgodnie z opisem w poprzedniej sekcji.
Weź pod uwagę te 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
}
}
}
Aby przetestować przekształcenie w funkcji MyViewModel
, podaj fałszywą instancję MyRepository
, która przekazuje dane do statycznej wartości List
reprezentującej dane do przekształcenia, jak pokazano w tym fragmencie:
class FakeMyRepository(): MyRepository {
private val items = (0..100).map(Any::toString)
private val pagingSourceFactory = items.asPagingSourceFactory()
val pagingSource = pagingSourceFactory()
}
Następnie możesz utworzyć test działania separatora, tak jak w tym fragmencie:
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.
}
Testy warstwy danych
Utwórz testy jednostkowe komponentów w warstwie danych, aby mieć pewność, że prawidłowo wczytują dane ze źródeł danych. Podaj fałszywe wersje zależności, aby sprawdzić, czy testowane komponenty działają prawidłowo w izolacji. Główne komponenty, które musisz przetestować w warstwie repozytorium, to PagingSource
i RemoteMediator
. Przykłady w kolejnych sekcjach opierają się na Paging with NetworkSample (Paging with NetworkSample).
Testy PagingSource
Testy jednostkowe Twojej implementacji PagingSource
obejmują skonfigurowanie instancji PagingSource
i wczytanie z niej danych za pomocą TestPager
.
Aby skonfigurować instancję PagingSource
do testowania, podaj konstruktorowi fałszywe dane. Dzięki temu masz kontrolę nad danymi w testach.
W tym przykładzie parametr RedditApi
jest interfejsem Retrofit, który określa żądania serwera i klasy odpowiedzi.
Fałszywa wersja może zaimplementować interfejs, zastąpić wszelkie wymagane funkcje i zapewnić wygodne metody konfigurowania reakcji w testach przez fałszywy obiekt.
Gdy fałszywe treści będą już na miejscu, skonfiguruj zależności i zainicjuj obiekt PagingSource
w teście. Poniższy przykład pokazuje inicjowanie obiektu FakeRedditApi
z listą postów testowych i testowanie instancji 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
umożliwia też:
- Przetestuj kolejne operacje wczytywania z urządzenia
PagingSource
:
@Test
fun test_consecutive_loads() = runTest {
val page = with(pager) {
refresh()
append()
append()
} as LoadResult.Page
assertThat(page.data)
.containsExactlyElementsIn(testPosts)
.inOrder()
}
- Przetestuj scenariusze błędów w
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()
}
}
Testy Remote Mediator
Celem testów jednostkowych RemoteMediator
jest sprawdzenie, czy funkcja load()
zwraca prawidłową wartość MediatorResult
.
Testy efektów ubocznych, takich jak wstawianie danych do bazy danych, najlepiej nadają się do testów integracji.
Pierwszym krokiem jest określenie zależności, których potrzebuje implementacja RemoteMediator
. Poniższy przykład przedstawia implementację RemoteMediator
, która wymaga bazy danych Room, interfejsu Retrofit i ciągu wyszukiwania:
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; ... } }
Możesz podać interfejs Retrofit i ciąg wyszukiwania w sposób pokazany w sekcji Testy PagingSource. Udostępnianie wersji próbnej bazy danych sal jest bardzo pracochłonne, dlatego łatwiej jest udostępnić implementację bazy danych w pamięci zamiast pełnej wersji testowej. Tworzenie bazy danych pomieszczenia wymaga obiektu Context
, więc musisz umieścić ten test RemoteMediator
w katalogu androidTest
i wykonać go za pomocą mechanizmu uruchamiania testowego AndroidJUnit4, aby miał dostęp do kontekstu aplikacji testowej. Więcej informacji o testach instrumentowanych znajdziesz w artykule o tworzeniu instrumentowanych testów jednostkowych.
Zdefiniuj funkcje dezaktywacji, aby mieć pewność, że stan nie będzie wyciekł między funkcjami testowymi. Zapewnia to spójność wyników między poszczególnymi uruchomieniami testów.
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(); } }
Następnym krokiem jest przetestowanie funkcji load()
. W tym przykładzie należy przetestować
3 przypadki:
- Pierwszy przypadek ma miejsce, gdy
mockApi
zwraca prawidłowe dane. Funkcjaload()
powinna zwracać wartośćMediatorResult.Success
, a właściwośćendOfPaginationReached
powinna mieć wartośćfalse
. - W drugim przypadku funkcja
mockApi
zwraca pomyślną odpowiedź, ale zwrócone dane są puste. Funkcjaload()
powinna zwracać wartośćMediatorResult.Success
, a właściwośćendOfPaginationReached
–true
. - Trzeci przypadek dotyczy sytuacji, w której
mockApi
zgłasza wyjątek podczas pobierania danych. Funkcjaload()
powinna zwrócić wartośćMediatorResult.Error
.
Aby przetestować pierwszy przypadek, wykonaj te czynności:
- Skonfiguruj
mockApi
z danymi postów, które mają być zwracane. - Zainicjuj obiekt
RemoteMediator
. - Przetestuj funkcję
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()); }
Drugi test wymaga, aby interfejs mockApi
zwracał pusty wynik. Po każdym uruchomieniu testu usuwasz dane z tabeli mockApi
, dlatego domyślnie zwracany jest pusty wynik.
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()); }
Końcowy test wymaga, aby mockApi
zgłosił wyjątek, aby test mógł sprawdzić, czy funkcja load()
prawidłowo zwraca wartość MediatorResult.Error
.
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)); }
Kompleksowe testy
Testy jednostkowe dają pewność, że poszczególne komponenty stron docelowych działają w izolacji. Z kolei kompleksowe testy dają większą pewność, że aplikacja działa jako całość. Te testy nadal będą wymagały kilku przykładowych zależności, ale zwykle obejmują większość kodu aplikacji.
W przykładzie w tej sekcji zastosowano pozorowaną zależność interfejsu API, aby uniknąć używania sieci w testach. Interfejs próbny jest skonfigurowany tak, aby zwracać spójny zestaw danych testowych, dzięki czemu testy są powtarzalne. Na podstawie tego, jak działają poszczególne zależności, jakie są spójne dane wyjściowe i jakiej wierności testów potrzebujesz, zdecyduj, które zależności zastąpić na ich próbnych implementacjach.
Napisz kod w taki sposób, aby można było łatwo zamieniać się w próbne wersje zależności. W przykładzie poniżej użyto podstawowej implementacji lokalizatora usług w celu określenia i zmiany zależności w razie potrzeby. W większych aplikacjach korzystanie z biblioteki wstrzykiwania zależności, takiej jak Hilt, może pomóc w zarządzaniu bardziej złożonymi wykresami zależności.
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; } } ); } }
Po skonfigurowaniu struktury testowej następnym krokiem jest sprawdzenie, czy dane zwracane przez implementację Pager
są prawidłowe. W ramach jednego testu obiekt Pager
wczytuje dane domyślne przy pierwszym wczytaniu strony, a inny – czy obiekt Pager
prawidłowo wczytuje dodatkowe dane na podstawie danych wejściowych użytkownika. W poniższym przykładzie test sprawdza, czy obiekt Pager
aktualizuje RecyclerView.Adapter
odpowiednią liczbą elementów zwracanych przez interfejs API, gdy użytkownik wpisze inny podreddit, aby go przeszukać.
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()); }); }
Testy instrumentalne powinny sprawdzać, czy dane wyświetlają się prawidłowo w interfejsie. Aby to zrobić, sprawdź, czy RecyclerView.Adapter
zawiera prawidłową liczbę elementów, lub przejrzyj poszczególne widoki wierszy i sprawdź, czy dane są prawidłowo sformatowane.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Strona z sieci i bazy danych
- Migrate to Paging 3 (Migracja do strony 3)
- Wczytywanie i wyświetlanie danych z podziałem na strony