测试 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 实现:

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

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

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

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

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

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

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

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

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

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

最后一项测试要求 mockApi 抛出异常,以便测试可以验证 load() 函数是否会正确返回 MediatorResult.Error

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

端到端测试

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

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

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

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

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

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

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

目前没有任何推荐文档页面。

请尝试您的 Google 账号。