আপনার পেজিং ইমপ্লিমেন্টেশন পরীক্ষা করুন

আপনার অ্যাপে পেজিং লাইব্রেরি প্রয়োগ করার সময়, একটি শক্তিশালী টেস্টিং স্ট্র্যাটেজি ব্যবহার করা উচিত। আপনি ডেটা লোডিং কম্পোনেন্ট যেমন PagingSource এবং RemoteMediator পরীক্ষা করে দেখুন যাতে সেগুলি প্রত্যাশা অনুযায়ী কাজ করে। এছাড়াও, আপনার পেজিং ইমপ্লিমেন্টেশনের সব কম্পোনেন্ট কোনও অপ্রত্যাশিত পার্শ্বপ্রতিক্রিয়া ছাড়াই একসাথে সঠিকভাবে কাজ করছে কিনা তা যাচাই করতে আপনাকে এন্ড-টু-এন্ড টেস্ট লিখতে হবে।

এই নির্দেশিকা থেকে আপনি জানতে পারবেন যে কীভাবে আপনার অ্যাপের বিভিন্ন আর্কিটেকচার লেয়ারে পেজিং লাইব্রেরি পরীক্ষা করবেন এবং কীভাবে আপনার সম্পূর্ণ পেজিং ইমপ্লিমেন্টেশনের জন্য এন্ড-টু-এন্ড পরীক্ষা লিখবেন।

UI লেয়ার টেস্ট

যেহেতু Compose, collectAsLazyPagingItems-এর মাধ্যমে ডিক্লেরেটিভভাবে পেজিং ডেটা ব্যবহার করে, তাই আপনার UI লেয়ার টেস্ট মূলত আপনার ViewModel-এর মাধ্যমে নির্গত Flow<PagingData<Value>>-এর উপর ফোকাস করতে পারে। UI-তে ডেটা আপনার প্রত্যাশা অনুযায়ী আছে কিনা তা যাচাই করার জন্য টেস্ট লিখতে, paging-testing ডিপেন্ডেন্সি অন্তর্ভুক্ত করুন। এতে Flow<PagingData<Value>>-এ asSnapshot এক্সটেনশন রয়েছে। এটি ল্যাম্বডা রিসিভারে 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 ব্যবহার করুন।
  • আপনার ViewModel-কে সেই Repository পাস করুন।

তারপর, আগের বিভাগে যেভাবে বলা হয়েছে সেইভাবে 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()

    // Expose as a function so a new PagingSource instance is
    // created each time it is called by the Pager
    fun 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.
}

Compose লোড স্ট্যাটাস টেস্ট করুন

আপনার Compose UI কম্পোনেন্ট কীভাবে বিভিন্ন লোড স্টেটে (যেমন LoadState.Loading বা LoadState.Error) আইসোলেশনে প্রতিক্রিয়া জানায় তা পরীক্ষা করতে, PagingData.empty এবং PagingData.from ব্যবহার করে MutableStateFlow-এর মাধ্যমে স্পষ্ট LoadStates সহ স্ট্যাটিক PagingData স্ন্যাপশট এমিট করুন:

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun userListScreen_displaysLoadingDataAndErrorStates() {
  val idle = LoadState.NotLoading(endOfPaginationReached = false)
  // Emit static PagingData snapshots to test UI state transitions without a
  // Pager or ViewModel.
  val pagingDataFlow = MutableStateFlow(PagingData.empty<User>())

  composeTestRule.setContent {
    val lazyPagingItems = pagingDataFlow.collectAsLazyPagingItems()
    UserListScreen(lazyPagingItems = lazyPagingItems)
  }

  // Verify the initial loading UI before any items arrive.
  pagingDataFlow.value = PagingData.empty(
    sourceLoadStates = LoadStates(
      refresh = LoadState.Loading,
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithTag("LoadingSpinner").assertIsDisplayed()

  // Verify that loaded items replace the loading indicator.
  pagingDataFlow.value = PagingData.from(
    data = listOf(User(id = "1", label = "User A")),
    sourceLoadStates = LoadStates(
      refresh = idle,
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithTag("LoadingSpinner").assertIsNotDisplayed()
  composeTestRule.onNodeWithText("User A").assertIsDisplayed()

  // Verify that a refresh failure surfaces the error message in the UI.
  pagingDataFlow.value = PagingData.empty(
    sourceLoadStates = LoadStates(
      refresh = LoadState.Error(IOException("Network unavailable")),
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithText("Network unavailable").assertIsDisplayed()
}

ডেটা লেয়ার টেস্ট

আপনার ডেটা লেয়ারের কম্পোনেন্টগুলির জন্য ইউনিট টেস্ট লিখুন যাতে সেগুলি আপনার ডেটা সোর্স থেকে ডেটা সঠিকভাবে লোড করে। ডিপেন্ডেন্সির নকল ভার্সন প্রদান করুন যাতে যাচাই করা যায় যে পরীক্ষা করা কম্পোনেন্টগুলি আলাদাভাবে সঠিকভাবে কাজ করছে। রিপোজিটরি লেয়ারে আপনাকে যে প্রধান কম্পোনেন্টগুলি পরীক্ষা করতে হবে সেগুলি হল PagingSource এবং RemoteMediator।

PagingSourceটি টেস্ট

আপনার PagingSource প্রয়োগের জন্য ইউনিট টেস্টে PagingSource ইনস্ট্যান্স সেট-আপ করা এবং TestPager-এর মাধ্যমে এটি থেকে ডেটা লোড করা অন্তর্ভুক্ত।

টেস্ট করার জন্য PagingSource ইনস্ট্যান্স সেট-আপ করতে, কনস্ট্রাক্টরকে নকল ডেটা প্রদান করুন। এটি আপনাকে আপনার টেস্টের ডেটার উপর নিয়ন্ত্রণ দেয়। নিম্নলিখিত উদাহরণে, RedditApi প্যারামিটার হল একটি Retrofit ইন্টারফেস যা সার্ভার অনুরোধ এবং রেসপন্স ক্লাসকে সংজ্ঞায়িত করে। একটি ফেক ভার্সন ইন্টারফেস প্রয়োগ করতে পারে, যেকোনও প্রয়োজনীয় ফাংশন ওভাররাইড করতে পারে এবং টেস্টে ফেক অবজেক্ট কীভাবে প্রতিক্রিয়া জানাবে তা কনফিগার করার জন্য সুবিধাজনক পদ্ধতি প্রদান করতে পারে।

ফেক অবজেক্ট তৈরি করার পরে, ডিপেন্ডেন্সি সেট-আপ করুন এবং টেস্টে PagingSource অবজেক্ট ইনিশিয়ালাইজ করুন। নিম্নলিখিত উদাহরণে দেখানো হয়েছে যে কীভাবে টেস্ট পোস্টের তালিকা সহ 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 প্রয়োগের জন্য কী কী ডিপেন্ডেন্সি প্রয়োজন তা নির্ধারণ করা। নিম্নলিখিত উদাহরণে RemoteMediator প্রয়োগের পদ্ধতি দেখানো হয়েছে যার জন্য রুম ডেটাবেস, রেট্রোফিট ইন্টারফেস ও সার্চ স্ট্রিং প্রয়োজন:

@OptIn(ExperimentalPagingApi::class)
class PageKeyedRemoteMediator(
  private val db: RedditDb,
  private val redditApi: RedditApi,
  private val subredditName: String
) : RemoteMediator&lt;Int, RedditPost&gt;() {
  ...
}

আপনি Retrofit ইন্টারফেস এবং সার্চ স্ট্রিং প্রদান করতে পারেন, যেমনটি PagingSource টেস্ট বিভাগে দেখানো হয়েছে। রুম ডেটাবেসের মক ভার্সন প্রদান করা খুবই জটিল, তাই সম্পূর্ণ মক ভার্সনের পরিবর্তে ডেটাবেসের ইন-মেমরি ইমপ্লিমেন্টেশন প্রদান করা সহজ হতে পারে। কারণ, রুম ডেটাবেস তৈরি করতে 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()
  }
}

পরবর্তী ধাপ হল load() ফাংশন পরীক্ষা করা। এই উদাহরণে, পরীক্ষা করার জন্য তিনটি কেস আছে:

  • প্রথম ক্ষেত্রে mockApi সঠিক ডেটা রিটার্ন করে। load() ফাংশন MediatorResult.Success রিটার্ন করবে এবং endOfPaginationReached প্রপার্টি false হতে হবে।
  • দ্বিতীয় ক্ষেত্রে, mockApi সফলভাবে উত্তর দিলেও, ফেরত পাঠানো ডেটা খালি থাকে। load() ফাংশনটি MediatorResult.Success রিটার্ন করবে এবং endOfPaginationReached প্রপার্টিটি true হতে হবে।
  • তৃতীয় ক্ষেত্রে, ডেটা ফেচ করার সময় mockApi ব্যতিক্রম দেখায়। load() ফাংশন MediatorResult.Error রিটার্ন করবে।

প্রথম কেসটি পরীক্ষা করতে এই ধাপগুলি অনুসরণ করুন:

  1. ফেরত পাঠানোর জন্য পোস্ট ডেটা সহ 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&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertFalse { (result as MediatorResult.Success).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&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertTrue { (result as MediatorResult.Success).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&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue {result is MediatorResult.Error }
}

এন্ড-টু-এন্ড টেস্ট

ইউনিট টেস্টের মাধ্যমে নিশ্চিত করা যায় যে প্রতিটি পেজিং কম্পোনেন্ট আলাদাভাবে কাজ করছে, তবে এন্ড-টু-এন্ড টেস্টের মাধ্যমে আরও ভালোভাবে নিশ্চিত করা যায় যে অ্যাপ্লিকেশনটি সামগ্রিকভাবে কাজ করছে। এইসব টেস্ট আপনার ডেটা লেয়ার (PagingSource বা RemoteMediator), ViewModel এবং Compose UI কোনও অপ্রত্যাশিত সাইড এফেক্ট ছাড়াই নিরবচ্ছিন্নভাবে ইন্টিগ্রেট হয়েছে কিনা তা যাচাই করতে সাহায্য করে। টেস্টের জন্য এখনও কিছু মক ডিপেন্ডেন্সি প্রয়োজন হবে, তবে সাধারণত সেগুলি আপনার অ্যাপ কোডের বেশিরভাগ অংশ কভার করবে।

এই বিভাগের উদাহরণে, টেস্টে নেটওয়ার্ক ব্যবহার এড়াতে মক API ডিপেন্ডেন্সি ব্যবহার করা হয়। মক API-কে এমনভাবে কনফিগার করা হয়েছে যাতে এটি টেস্ট ডেটার একটি সামঞ্জস্যপূর্ণ সেট রিটার্ন করে, যার ফলে টেস্ট রিপিট করা যায়। এন্ড-টু-এন্ড টেস্টের জন্য, আপনি সাধারণত আপনার আসল নেটওয়ার্ক API-এর পরিবর্তে একটি নকল API ব্যবহার করেন, তবে আপনি এখনও পেজিং লাইব্রেরিকে আসল ফেচিং এবং লোকাল ডেটাবেস ক্যাশিং (যদি একটি RemoteMediator ব্যবহার করেন) আপনার টেস্টের বিশ্বস্ততা বজায় রাখতে দেন।

এমনভাবে কোড লিখুন যাতে আপনার ডিপেন্ডেন্সির মক ভার্সন সহজেই সোয়্যাপ করতে পারেন। নিম্নলিখিত উদাহরণে একটি বেসিক পরিষেবা লোকেটার ইমপ্লিমেন্টেশন ব্যবহার করা হয়েছে এবং একটি মক API-এর মাধ্যমে টেস্ট সেট-আপ করা হয়েছে যাতে যাচাই করা যায় যে Compose স্ক্রিন সঠিকভাবে পেজ করা ডেটা ব্যবহার ও ডিসপ্লে করছে কিনা। আরও বড় অ্যাপে, Hilt-এর মতো ডিপেন্ডেন্সি ইনজেকশন লাইব্রেরি ব্যবহার করলে, আরও জটিল ডিপেন্ডেন্সি গ্রাফ ম্যানেজ করতে সাহায্য করতে পারে।

টেস্ট স্ট্রাকচার সেট-আপ করার পরে, পরবর্তী ধাপ হল Pager ইমপ্লিমেন্টেশনের মাধ্যমে ফেরত পাঠানো ডেটা সঠিক কিনা তা যাচাই করা। একটি টেস্টের মাধ্যমে যাচাই করা উচিত যে স্ক্রিন প্রথমবার লোড হওয়ার সময় Compose UI সঠিক আইটেম দিয়ে পপুলেট হয় কিনা এবং অন্য একটি টেস্টের মাধ্যমে যাচাই করা উচিত যে UI ব্যবহারকারীর ইন্টার‍্যাকশনের ভিত্তিতে অতিরিক্ত ডেটা সঠিকভাবে লোড করে কিনা।

নিচের উদাহরণে, টেস্টটি যাচাই করে যে UI প্রত্যাশিত পৃষ্ঠার ডেটা দেখায়।

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertDoesNotExist
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RedditScreenTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    private val postFactory = PostFactory()
    private val mockApi = MockRedditApi()

    @Before
    fun setup() {
        // Pre-populate the mock API with test data for the default subreddit
        mockApi.addPost(postFactory.createRedditPost(subreddit = "androiddev", title = "Jetpack Compose Paging"))

        // Swap your real dependency injection module/Service Locator with the mock API
        ServiceLocator.swap(
            object : DefaultServiceLocator(useInMemoryDb = true) {
                override fun getRedditApi(): RedditApi = mockApi
            }
        )
    }

    @Test
    fun loadsTheDefaultResults() = runTest {
        // 1. Set the Compose UI content
        composeTestRule.setContent {
            MyTheme {
                // Assume that this composable uses `collectAsLazyPagingItems()` internally
                RedditScreen(initialSubreddit = "androiddev")
            }
        }

        // 2. Wait for the asynchronous Paging loads to complete
        composeTestRule.waitUntilExactlyOneExists(
            matcher = hasText("Jetpack Compose Paging"),
            timeoutMillis = 5000
        )

        // 3. Assert that the loaded paged items are displayed correctly on screen
        composeTestRule.onNodeWithText("Jetpack Compose Paging").assertIsDisplayed()
    }

    @Test
    fun loadsNewDataBasedOnUserInput() = runTest {
        // Add data for a different subreddit to the mock API
        mockApi.addPost(postFactory.createRedditPost(subreddit = "compose", title = "Compose Testing"))

        composeTestRule.setContent {
            MyTheme {
                RedditScreen(initialSubreddit = "androiddev")
            }
        }

        // Wait for the initial load to finish
        composeTestRule.waitUntilExactlyOneExists(hasText("Jetpack Compose Paging"))

        // Simulate user entering a new subreddit in a text field and clicking search
        composeTestRule.onNodeWithTag("SubredditInput").performTextClearance()
        composeTestRule.onNodeWithTag("SubredditInput").performTextInput("compose")
        composeTestRule.onNodeWithTag("SearchButton").performClick()

        // Wait for the new paged data to load
        composeTestRule.waitUntilExactlyOneExists(
            matcher = hasText("Compose Testing"),
            timeoutMillis = 5000
        )

        // Assert the old data is gone and the new data is displayed
        composeTestRule.onNodeWithText("Jetpack Compose Paging").assertDoesNotExist()
        composeTestRule.onNodeWithText("Compose Testing").assertIsDisplayed()
    }
}

যেহেতু Flow<PagingData> অ্যাসিঙ্ক্রোনাসভাবে ডেটা লোড করে, তাই অ্যাসারশন করার আগে আপনাকে পেজিং লাইব্রেরিকে প্রাথমিক লোড ফেচ করার এবং এটি collectAsLazyPagingItems-এ এমিট করার সময় দিতে হবে। এটি করতে, composeTestRule.waitUntil বা waitUntilExactlyOneExists ব্যবহার করুন, যেমনটি পূর্ববর্তী উদাহরণে দেখানো হয়েছে।

ডেটা লোড হওয়ার পরে, আপনি onNodeWithText ব্যবহার করে সরাসরি Compose সিম্যান্টিক ট্রি-এর সাথে অ্যাসার্ট করতে পারবেন, যাতে আইটেমগুলি আপনার LazyColumn-এ আসলে রেন্ডার করা হয়েছে কিনা তা যাচাই করা যায়।

অতিরিক্ত রিসোর্স

কন্টেন্ট দেখে