测试 Paging 实现

在应用中实现 Paging 库需要辅以稳健的测试策略。您应测试 PagingSourceRemoteMediator 等数据加载组件,以确保这些组件按预期运行。您还应编写端到端测试,以验证 Paging 实现中的所有组件能否正确协作,而不产生意外的副效应。

本指南将介绍如何在应用的不同架构层中测试 Paging 库,以及如何为整个 Paging 实现编写端到端测试。

界面层测试

使用 Paging 库提取的数据会作为 Flow<PagingData<Value>> 在界面中使用。若要编写测试以验证界面中的数据是否符合您的预期,请添加 paging-testing 依赖项。它包含对 Flow<PagingData<Value>>asSnapshot() 扩展,并且其 lambda 接收器中提供了一些 API,这些 API 用于模拟滚动互动。它会返回一个由模拟的滚动互动生成的标准 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" }  } 

测试转换

您还应编写单元测试,以涵盖您应用于 PagingData 流的所有转换。使用 asPagingSourceFactory 扩展。此扩展适用于以下数据类型:

  • List<Value>
  • Flow<List<Value>>

具体选择使用哪一个扩展取决于您要测试的内容。 使用:

  • List<Value>.asPagingSourceFactory():如果要测试数据的静态转换(如 map()insertSeparators())。
  • Flow<List<Value>>.asPagingSourceFactory():如果要测试数据更新(例如写入后备数据源)对分页流水线有何影响。

如需使用上述任何一种扩展,请遵循以下模式:

  • 根据您的需求使用适当的扩展来创建 PagingSourceFactory
  • Repository虚构实例中使用返回的 PagingSourceFactory
  • 将该 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()  val 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. } 

数据层测试

为数据层中的组件编写单元测试,以确保这些组件能从数据源正确加载数据。提供依赖项的虚构版本,以验证被测组件在单独运行时是否正常。您需要在代码库层中测试的主要组件是 PagingSourceRemoteMediator。后续部分中的示例基于使用网络进行分页的示例

PagingSource 测试

PagingSource 实现的单元测试涉及设置 PagingSource 实例,以及使用 TestPager 从该实例加载数据。

如需设置 PagingSource 实例以进行测试,请向构造函数提供虚构数据。这样,您就可以控制测试中的数据。在以下示例中,RedditApi 参数是一个 Retrofit 该接口定义了服务器请求和响应类。 虚构版本可以实现该接口、替换任何必需函数,并提供便捷方法来配置虚构对象在测试中的反应方式。

创建好虚构对象后,应在测试中设置依赖项并初始化 PagingSource 对象。以下示例演示了如何使用一系列测试 post 来初始化 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 测试

RemoteMediator 单元测试的目的是验证 load() 函数是否会返回正确的 MediatorResult。副效应测试(例如将数据插入数据库)更适合集成测试

第一步是确定 RemoteMediator 实现需要的依赖项。以下示例演示了需要 Room 数据库、Retrofit 接口和搜索字符串的 RemoteMediator 实现:

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;  ...  } }

您可以提供 Retrofit 接口和搜索字符串,如 PagingSource 测试部分所示。提供 Room 数据库的模拟版本非常复杂,而更简单的做法是提供数据库的内存中实现,而不是完整的模拟版本。由于创建 Room 数据库需要 Context 对象,您必须将此 RemoteMediator 测试放在 androidTest 目录中,并使用 AndroidJUnit4 测试运行程序来执行此测试,使其能够访问测试应用上下文。如需详细了解插桩测试,请参阅构建插桩单元测试

定义拆解函数,以确保状态不会在测试函数之间泄漏。这可确保测试运行之间结果的一致。

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();  } }

下一步是测试 load() 函数。在此示例中,要测试三种情况:

  • 第一种情况是 mockApi 返回有效数据。load() 函数应返回 MediatorResult.SuccessendOfPaginationReached 属性应为 false
  • 第二种情况是 mockApi 返回成功响应,但返回的数据为空。load() 函数应返回 MediatorResult.SuccessendOfPaginationReached 属性应为 true
  • 第三种情况是 mockApi 在获取数据时抛出异常。load() 函数应返回 MediatorResult.Error

请按以下步骤测试第一种情况:

  1. 使用要返回的 post 数据设置 mockApi
  2. 初始化 RemoteMediator 对象:
  3. 测试 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()); }

第二个测试要求 mockApi 返回一个空结果。由于您在每次运行测试后都清除 mockApi 中的数据,因此默认情况下它会返回一个空结果。

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()); }

最后一项测试要求 mockApi 抛出异常,以便测试可以验证 load() 函数是否会正确返回 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)); }

端到端测试

单元测试可确保各个 Paging 组件在单独运行时可以正常工作,而端到端测试则可确保应用作为一个整体运行正常。这些测试仍需要一些模拟依赖项,但通常会涵盖您的大部分应用代码。

本部分中的示例使用模拟 API 依赖项,以避免在测试中使用网络。模拟 API 配置为返回一组一致的测试数据,从而使测试具有可重复性。根据每个依赖项的功能、输出的一致性以及测试所需的准确性,确定要为模拟实现换出哪些依赖项。

以方便换入依赖项模拟版本的方式编写代码。以下示例使用基本的服务定位器实现来按需提供和更改依赖项。在较大的应用中,使用 Hilt 等依赖项注入库,有助于管理更复杂的依赖项关系图。

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;  }  }  );  } }

设置测试结构后,下一步是验证 Pager 实现返回的数据是否正确。应有一个测试来确保在页面首次加载时 Pager 对象会加载默认数据,还应有一个测试来验证 Pager 对象是否会根据用户输入正确加载其他数据。在以下示例中,测试会验证:当用户输入不同的 subreddit 进行搜索时,Pager 对象是否会使用从 API 返回的正确数量的项来更新 RecyclerView.Adapter

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());  }); }

插桩测试应验证数据是否在界面中正确显示。您可以通过以下两种方式之一来实现此目的:验证 RecyclerView.Adapter 中是否存在正确数量的项,或者遍历各个行视图并验证数据格式是否正确。