Die Implementierung der Paging-Bibliothek in Ihrer App sollte mit einem soliden
Teststrategie. Testen Sie Komponenten zum Laden von Daten, z. B.
PagingSource
und
RemoteMediator
damit sie wie erwartet funktionieren. Sie sollten zudem End-to-End-Tests
Prüfen, ob alle Komponenten in der Paging-Implementierung ordnungsgemäß funktionieren
ohne unerwartete Nebenwirkungen.
In diesem Leitfaden wird erläutert, wie Sie die Paging-Bibliothek in den verschiedenen Architekturebenen der Anwendung und wie Sie sie programmieren, End-to-End-Tests für Ihre gesamte Paging-Implementierung.
Tests der UI-Ebene
Mit der Paging-Bibliothek abgerufene Daten werden in der Benutzeroberfläche wie folgt verarbeitet:
ein Flow<PagingData<Value>>
.
Um Tests zu schreiben, um zu überprüfen, ob die Daten in der Benutzeroberfläche Ihren Erwartungen entsprechen, fügen Sie den Parameter
paging-testing
-Abhängigkeit.
Sie enthält die Erweiterung asSnapshot()
auf einem Flow<PagingData<Value>>
. Es
bietet APIs in seinem Lambda-Empfänger, die Simulations-Scrollen ermöglichen.
Interaktionen. Es wird ein standardmäßiges List<Value>
zurückgegeben, das durch das Scrollen erzeugt wird.
simulierten Interaktionen, sodass Sie die Daten
enthält die erwarteten Elemente, die bei diesen Interaktionen generiert werden.
Dies wird im folgenden Snippet veranschaulicht:
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
)
}
Alternativ können Sie scrollen, bis ein bestimmtes Prädikat erfüllt ist, wie in der Abbildung Snippet unten:
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" }
}
Transformationen testen
Sie sollten auch Einheitentests schreiben, die alle Transformationen abdecken, die Sie anwenden
den Stream PagingData
. asPagingSourceFactory
verwenden
. Diese Erweiterung ist für die folgenden Datentypen verfügbar:
List<Value>
.Flow<List<Value>>
.
Welche Erweiterung Sie verwenden möchten, hängt davon ab, was Sie testen möchten. Verwendung:
List<Value>.asPagingSourceFactory()
: Wenn Sie eine statische Transformationen wiemap()
undinsertSeparators()
für Daten.Flow<List<Value>>.asPagingSourceFactory()
: Wenn Sie testen möchten, wie Updates wie das Schreiben in die unterstützende Datenquelle, wirkt sich zu erstellen.
Wenn Sie eine dieser Erweiterungen verwenden möchten, folgen Sie dem folgenden Muster:
- Erstellen Sie die
PagingSourceFactory
mit der passenden Erweiterung für Ihr Anforderungen. - Zurückgegebene
PagingSourceFactory
verwenden in eine Fälschung für deinRepository
-Konto. - Übergib diesen
Repository
an deinViewModel
.
ViewModel
kann dann wie im vorherigen Abschnitt beschrieben getestet werden.
Sehen Sie sich die folgende ViewModel
an:
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
}
}
}
Geben Sie eine fiktive Instanz von an, um die Transformation in MyViewModel
zu testen.
MyRepository
, die an eine statische List
delegiert, die die zu verwendenden Daten darstellt
wie im folgenden Snippet gezeigt:
class FakeMyRepository(): MyRepository {
private val items = (0..100).map(Any::toString)
private val pagingSourceFactory = items.asPagingSourceFactory()
val pagingSource = pagingSourceFactory()
}
Sie können dann wie im folgenden Snippet einen Test für die Trennzeichenlogik schreiben:
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.
}
Datenschichttests
Schreiben Sie Einheitentests für die Komponenten in der Datenschicht, um sicherzustellen,
und laden Sie die Daten
entsprechend aus Ihren Datenquellen. Bereitstellen
gefälschte Versionen von
um zu überprüfen, ob die zu testenden Komponenten
Isolation. Die Hauptkomponenten, die Sie auf der Repository-Ebene testen müssen, sind:
PagingSource
und RemoteMediator
. Die Beispiele in den folgenden Abschnitten basieren auf den
Paging mit Netzwerk
Leseprobe.
PagingSource-Tests
Bei Unittests für deine PagingSource
-Implementierung muss Folgendes eingerichtet werden:
PagingSource
-Instanz und das Laden von Daten aus ihr mit einer TestPager
.
Um die PagingSource
-Instanz für Tests einzurichten, stellen Sie dem
-Konstruktor. So haben Sie die Kontrolle über die Daten in Ihren Tests.
Im folgenden Beispiel wird der RedditApi
ist ein Retrofit-Parameter.
Schnittstelle, die die Serveranfragen und Antwortklassen definiert.
Eine fiktive Version kann
die Schnittstelle implementieren, alle erforderlichen Funktionen überschreiben,
und bieten praktische Methoden, um zu konfigurieren, wie das fiktive Objekt reagieren soll.
Tests durchführen.
Nachdem die Fakes vorhanden sind, richten Sie die Abhängigkeiten ein und initialisieren Sie
PagingSource
-Objekt im Test. Im folgenden Beispiel sehen Sie,
Initialisieren des FakeRedditApi
-Objekts mit einer Liste von Testbeiträgen und Tests
die Instanz 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()
}
}
Mit TestPager
können Sie außerdem Folgendes tun:
- Teste aufeinanderfolgende Ladevorgänge von deinem
PagingSource
:
@Test
fun test_consecutive_loads() = runTest {
val page = with(pager) {
refresh()
append()
append()
} as LoadResult.Page
assertThat(page.data)
.containsExactlyElementsIn(testPosts)
.inOrder()
}
- Testen Sie Fehlerszenarien in Ihrem
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-Tests
Das Ziel der RemoteMediator
-Einheitentests besteht darin, zu überprüfen, ob der load()
die korrekte
MediatorResult
Tests auf Nebenwirkungen, z. B. Daten, die in die Datenbank eingefügt werden,
Diese Funktion ist besser für Integrationstests geeignet.
Bestimmen Sie zuerst, welche Abhängigkeiten Ihr RemoteMediator
Implementierungsbedarf. Das folgende Beispiel zeigt eine RemoteMediator
Implementierung, die eine Raumdatenbank, eine Retrofit-Oberfläche und eine Suche erfordert
String:
@OptIn(ExperimentalPagingApi::class)
class PageKeyedRemoteMediator(
private val db: RedditDb,
private val redditApi: RedditApi,
private val subredditName: String
) : RemoteMediator<Int, RedditPost>() {
...
}
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;
...
}
}
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;
...
}
}
Sie können die Retrofit-Oberfläche und den Suchstring wie in den
im Abschnitt PagingSource Tests. Mock-Version bereitstellen
der Room-Datenbank ist sehr beteiligt, daher kann es einfacher sein,
In-Memory-Implementierung von
anstelle einer vollständigen Mock-Version. Weil eine Raumdatenbank erstellt wird
ein Context
-Objekt erfordert, müssen Sie
diesen RemoteMediator
-Test im Verzeichnis androidTest
ablegen und ausführen
mit dem AndroidJUnit4-Test-Runner, damit dieser Zugriff auf eine Test-App hat
Kontext. Weitere Informationen zu instrumentierten Tests finden Sie unter Instrumentierte Tests erstellen
Unittests.
Teardown-Funktionen definieren, um sicherzustellen, dass zwischen den Tests keine Zustandslecks auftreten Funktionen. Dies sorgt für konsistente Ergebnisse zwischen den Testläufen.
@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()
}
}
@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();
}
}
@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();
}
}
Im nächsten Schritt testen Sie die Funktion load()
. In diesem Beispiel gibt es drei
zu testende Fälle:
- Im ersten Fall gibt
mockApi
gültige Daten zurück. Die Funktionload()
sollteMediatorResult.Success
und derendOfPaginationReached
-Wert solltefalse
sein. - Im zweiten Fall gibt
mockApi
eine erfolgreiche Antwort zurück, aber der zurückgegebene Daten leer sind. Die Funktionload()
sollte Folgendes zurückgeben:MediatorResult.Success
und das AttributendOfPaginationReached
true
. - Der dritte Fall tritt ein, wenn
mockApi
beim Abrufen der Daten eine Ausnahme ausgibt. Die Funktionload()
sollteMediatorResult.Error
zurückgeben.
Führe die folgenden Schritte aus, um den ersten Fall zu testen:
- Richten Sie die
mockApi
mit den Post-Daten ein, die zurückgegeben werden sollen. - Initialisieren Sie das
RemoteMediator
-Objekt. - Testen Sie die Funktion
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 }
}
@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);
}
@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());
}
Für den zweiten Test muss mockApi
ein leeres Ergebnis zurückgeben. Weil du
nach jedem Testlauf aus mockApi
löschen, wird eine leere
wird standardmäßig angezeigt.
@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 }
}
@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);
}
@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());
}
Im letzten Test muss mockApi
eine Ausnahme auslösen, damit der Test
Prüfen Sie, ob die load()
-Funktion MediatorResult.Error
korrekt zurückgibt.
@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 }
}
@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);
}
@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));
}
End-to-End-Tests
Einheitentests bieten die Gewissheit, dass die einzelnen Paging-Komponenten aber End-to-End-Tests bieten mehr Sicherheit, als Ganzes funktioniert. Für diese Tests sind immer noch simulierte Abhängigkeiten erforderlich, aber Sie decken in der Regel den Großteil Ihres App-Codes ab.
Im Beispiel in diesem Abschnitt wird eine fiktive API-Abhängigkeit verwendet, um die Verwendung des in Tests. Die Mock API ist so konfiguriert, dass ein konsistenter Satz von Tests zurückgegeben wird. was zu wiederholbaren Tests führt. Abhängigkeiten festlegen, die ausgetauscht werden sollen simulierte Implementierungen basierend darauf, was die einzelnen Abhängigkeiten bewirken, wie konsistent ihre Ausgabe und wie viel Genauigkeit Sie für Ihre Tests benötigen.
Schreiben Sie Ihren Code so, dass Sie problemlos Scheinversionen Ihrer Abhängigkeiten. Im folgenden Beispiel wird eine einfache Service Locator-Anwendung Implementierung, um Ihren Nutzern und die Abhängigkeiten nach Bedarf ändern. In größeren Apps mithilfe einer Abhängigkeitsinjektion eine Bibliothek wie Hilt, und komplexere Abhängigkeitsdiagramme.
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
}
)
}
}
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;
}
}
);
}
}
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;
}
}
);
}
}
Nachdem Sie die Teststruktur eingerichtet haben,
müssen Sie prüfen, ob die Daten
Pager
-Implementierung zurückgegeben wird, ist korrekt. Ein Test sollte sicherstellen,
Das Pager
-Objekt lädt die Standarddaten, wenn die Seite zum ersten Mal geladen wird, und ein anderes
sollte geprüft werden, ob das Pager
-Objekt zusätzliche Daten korrekt lädt,
auf Nutzereingaben. Im folgenden Beispiel wird durch den Test geprüft, ob Pager
-Objekt aktualisiert RecyclerView.Adapter
mit der richtigen Anzahl von Elementen
der von der API zurückgegeben wird, wenn der Nutzer ein anderes Subreddit für die Suche eingibt.
@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)
}
}
@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());
});
}
@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());
});
}
Mit instrumentierten Tests sollte überprüft werden, ob die Daten auf der Benutzeroberfläche korrekt angezeigt werden. Das sollten Sie tun:
indem Sie prüfen, ob die richtige Anzahl von Elementen im
RecyclerView.Adapter
oder durch Iterieren durch die einzelnen Zeilenansichten und
Prüfen, ob die Daten richtig formatiert sind.
Derzeit liegen keine Empfehlungen vor.
Versuchen Sie, sich bei Ihrem Google-Konto anzumelden.