আপনার পেজিং বাস্তবায়ন পরীক্ষা করুন

আপনার অ্যাপে পেজিং লাইব্রেরি প্রয়োগ করার সাথে একটি শক্তিশালী টেস্টিং কৌশল থাকা উচিত। 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 তৈরি করুন।
  • ফেরত আসা PagingSourceFactory টি আপনার Repository এর একটি নকল সংস্করণে ব্যবহার করুন।
  • ওই 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()

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

ডেটা লেয়ার পরীক্ষা

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

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

আপনি PagingSource টেস্ট বিভাগে প্রদর্শিত পদ্ধতি অনুযায়ী Retrofit ইন্টারফেস এবং সার্চ স্ট্রিং প্রদান করতে পারেন। Room ডাটাবেসের একটি মক সংস্করণ প্রদান করা বেশ জটিল, তাই একটি সম্পূর্ণ মক সংস্করণের পরিবর্তে ডাটাবেসের একটি ইন-মেমরি ইমপ্লিমেন্টেশন প্রদান করা সহজতর হতে পারে। যেহেতু একটি Room ডাটাবেস তৈরি করার জন্য একটি Context অবজেক্টের প্রয়োজন হয়, তাই আপনাকে এই RemoteMediator টেস্টটি androidTest ডিরেক্টরিতে রাখতে হবে এবং AndroidJUnit4 টেস্ট রানার দিয়ে এটি এক্সিকিউট করতে হবে, যাতে এটি একটি টেস্ট অ্যাপ্লিকেশন কনটেক্সট অ্যাক্সেস করতে পারে। ইন্সট্রুমেন্টেড টেস্ট সম্পর্কে আরও তথ্যের জন্য, "Build instrumented unit tests" দেখুন।

টেস্ট ফাংশনগুলোর মধ্যে যেন স্টেট লিক না হয়, তা নিশ্চিত করতে টিয়ার-ডাউন ফাংশন সংজ্ঞায়িত করুন। এটি টেস্ট রানগুলোর মধ্যে সামঞ্জস্যপূর্ণ ফলাফল নিশ্চিত করে।

@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 একটি exception থ্রো করতে হবে, যাতে পরীক্ষাটি যাচাই করতে পারে যে 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 কোনো অপ্রত্যাশিত পার্শ্বপ্রতিক্রিয়া ছাড়াই নির্বিঘ্নে একীভূত হয়। এই টেস্টগুলোর জন্য কিছু মক ডিপেন্ডেন্সির প্রয়োজন হবে, তবে সাধারণত এগুলো আপনার অ্যাপের বেশিরভাগ কোডই কভার করবে।

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

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

টেস্টের কাঠামো তৈরি করার পর, পরবর্তী ধাপ হলো Pager ইমপ্লিমেন্টেশন থেকে প্রাপ্ত ডেটা সঠিক কিনা তা যাচাই করা। একটি টেস্টের মাধ্যমে যাচাই করতে হবে যে স্ক্রিনটি প্রথম লোড হওয়ার সময় কম্পোজ 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 ব্যবহার করুন।

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

অতিরিক্ত সম্পদ

বিষয়বস্তু দেখুন

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}