在应用中实现 Paging 库需要辅以稳健的测试策略。您应测试 PagingSource 和 RemoteMediator 等数据加载组件,以确保这些组件按预期运行。您还应编写端到端测试,以验证 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.
}
数据层测试
为数据层中的组件编写单元测试,以确保这些组件能从数据源正确加载数据。提供依赖项的虚构版本,以验证被测组件在单独运行时是否正常。您需要在代码库层中测试的主要组件是 PagingSource 和 RemoteMediator。后续部分中的示例基于使用网络进行分页的示例。
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.Success,endOfPaginationReached属性应为false。
- 第二种情况是 mockApi返回成功响应,但返回的数据为空。load()函数应返回MediatorResult.Success,endOfPaginationReached属性应为true。
- 第三种情况是 mockApi在获取数据时抛出异常。load()函数应返回MediatorResult.Error。
请按以下步骤测试第一种情况:
- 使用要返回的 post 数据设置 mockApi。
- 初始化 RemoteMediator对象:
- 测试 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 中是否存在正确数量的项,或者遍历各个行视图并验证数据格式是否正确。
为您推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- 从网络和数据库加载页面
- 迁移到 Paging 3
- 加载并显示分页数据
