v2 टेस्टिंग एपीआई की मदद से, टेस्ट एनवायरमेंट को कॉन्फ़िगर करना

Compose टेस्टिंग एपीआई के v2 वर्शन (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest वगैरह) अब उपलब्ध हैं. इनसे कोरूटीन के एक्ज़ीक्यूशन को बेहतर तरीके से कंट्रोल किया जा सकता है. इस अपडेट से, पूरे एपीआई प्लैटफ़ॉर्म का डुप्लीकेट नहीं बनता. सिर्फ़ उन एपीआई को अपडेट किया गया है जो टेस्ट एनवायरमेंट को सेट अप करते हैं.

v1 एपीआई अब काम नहीं करते. हमारा सुझाव है कि आप नए एपीआई पर माइग्रेट करें. माइग्रेट करने से यह पुष्टि होती है कि आपके टेस्ट, स्टैंडर्ड कोरूटीन के व्यवहार के मुताबिक हैं. साथ ही, इससे आने वाले समय में, साथ काम करने से जुड़ी समस्याओं से बचा जा सकता है. बंद किए गए v1 एपीआई की सूची देखने के लिए, एपीआई मैपिंग देखें.

v1 एपीआई, UnconfinedTestDispatcher पर निर्भर थे. वहीं, v2 एपीआई, कंपोज़िशन को चलाने के लिए डिफ़ॉल्ट रूप से StandardTestDispatcher का इस्तेमाल करते हैं. इस बदलाव से, Compose टेस्ट का व्यवहार स्टैंडर्ड runTest एपीआई के साथ अलाइन हो जाता है. साथ ही, कोरूटीन के लागू होने के क्रम पर साफ़ तौर पर कंट्रोल मिलता है.

टेस्ट एनवायरमेंट को कॉन्फ़िगर करना

Compose test v2 एपीआई, टेस्ट एनवायरमेंट को पसंद के मुताबिक बनाने के लिए ComposeUiTestConfig का इस्तेमाल करते हैं. टेस्ट के लिए सेटअप फ़ंक्शन बनाने वाले एपीआई, जैसे कि createComposeRule, runComposeUiTest, और अन्य संबंधित एपीआई, ComposeUiTestConfig को स्वीकार करते हैं. यह कॉन्फ़िगरेशन ऑब्जेक्ट, एनवायरमेंट से जुड़े एपीआई, जैसे कि effectContext, runTestContext, और testTimeout को एक ऑब्जेक्ट में इकट्ठा करता है.

कॉन्फ़िगरेशन मॉडल, inputMode को भी मैनेज करता है. Compose test v2 एपीआई, हर टेस्ट की शुरुआत में डिफ़ॉल्ट रूप से InputMode.Touch लागू करते हैं. इससे यह पक्का किया जाता है कि टेस्ट के नतीजे डिटरमिनिस्टिक हों और टेस्ट के बीच इनपुट मोड की स्थिति लीक न हो.

ComposeUiTestConfig, Compose testing v2 API का हिस्सा है. यह डिफ़ॉल्ट रूप से StandardTestDispatcher का इस्तेमाल करता है. अगर आपके टेस्ट में v1 एपीआई का इस्तेमाल किया जाता है, तो ComposeUiTestConfig को अपनाने से पहले v2 टेस्टिंग एपीआई पर माइग्रेट करें लेख पढ़ें.

ComposeUiTestConfig पर माइग्रेट करें

टेस्ट में सेटअप फ़ंक्शन बनाने के लिए, ओवरलोड के अंदर कई ओवरलोड, जो अलग-अलग कॉन्फ़िगरेशन पैरामीटर स्वीकार करते हैं -- जैसे कि effectContext, runTestContext या testTimeout -- अब काम नहीं करते. ComposeUiTestConfig का इस्तेमाल करने के लिए, अपने टेस्ट अपडेट करें. इसके लिए, यहां दिया गया उदाहरण देखें:

डिफ़ॉल्ट इनपुट मोड

अगर टेस्ट शुरू होने से पहले, इंस्ट्रुमेंटेशन एपीआई के ज़रिए कॉन्फ़िगर किए गए नॉन-टच इनपुट मोड पर टेस्ट निर्भर करते हैं, तो माइग्रेशन के दौरान टेस्ट फ़ेल हो सकते हैं. टेस्ट के लिए सेटअप फ़ंक्शन में, सिस्टम हर टेस्ट की शुरुआत में डिफ़ॉल्ट रूप से InputMode.Touch को लागू करता है. इससे, ज़्यादा सटीक नतीजे मिलते हैं और स्टेट लीक होने से रोका जा सकता है. साथ ही, डिवाइस की मौजूदा स्थिति और टेस्ट से पहले के सेटअप को बदला जा सकता है.

इसे ठीक करने के लिए, ComposeUiTestConfig में ज़रूरी इनपुट मोड की जानकारी दें:

class FocusTest {
    @get:Rule
    val rule = createComposeRule(
        config = ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    )

    @Test
    fun testFocus() {}
}

पूरी टेस्ट क्लास के बजाय अलग-अलग टेस्ट केस के लिए इनपुट मोड को कॉन्फ़िगर करने के लिए, ComposeUiTestConfig को runComposeUiTest पर पास करें:

class FocusTest {
    @Test
    fun testTouchMode() = runComposeUiTest {
        // Runs with the default InputMode.Touch
    }

    @Test
    fun testKeyboardMode() = runComposeUiTest(
        ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    ) {
        // Runs with InputMode.Keyboard
    }
}

माइग्रेशन से जुड़ी अन्य समस्याओं और उनके समाधान के लिए, आम तौर पर होने वाली गड़बड़ियां और उन्हें कैसे ठीक करें लेख पढ़ें.

v2 टेस्टिंग एपीआई पर माइग्रेट करना

v2 एपीआई पर अपग्रेड करते समय, आम तौर पर Find + Replace का इस्तेमाल करके, पैकेज इंपोर्ट को अपडेट किया जा सकता है. साथ ही, डिस्पैचर में किए गए नए बदलावों को लागू किया जा सकता है.

इसके अलावा, Gemini से Compose टेस्टिंग एपीआई के v2 पर माइग्रेट करने के लिए कहें. इसके लिए, यह प्रॉम्प्ट इस्तेमाल करें:

v1 टेस्टिंग एपीआई से v2 टेस्टिंग एपीआई पर माइग्रेट करना

इस प्रॉम्प्ट में, v2 टेस्टिंग एपीआई पर माइग्रेट करने के लिए इस गाइड का इस्तेमाल किया जाएगा.

Migrate to Compose testing v2 APIs using the official
migration guide.

एआई प्रॉम्प्ट का इस्तेमाल करना

एआई प्रॉम्प्ट का इस्तेमाल, Android Studio में Gemini के साथ किया जाना चाहिए.

Studio में Gemini के बारे में ज़्यादा जानने के लिए, यहां जाएं: https://developer.android.com/studio/gemini/overview

बंद किए जा चुके v1 एपीआई को उनके v2 वर्शन के साथ मैप करने के लिए, इस टेबल का इस्तेमाल करें:

अब काम नहीं करता (v1)

बदलाव (v2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

पुराने सिस्टम के साथ काम करने की सुविधा और अपवाद

मौजूदा v1 एपीआई अब काम नहीं करते. हालांकि, मौजूदा व्यवहार को बनाए रखने और नुकसान पहुंचाने वाले बदलावों को रोकने के लिए, UnconfinedTestDispatcher का इस्तेमाल जारी रखें.

सिर्फ़ इस मामले में डिफ़ॉल्ट ऐक्शन और सेटिंग में बदलाव किया गया है:

AndroidComposeUiTestEnvironment क्लास में कंपोज़िशन चलाने के लिए इस्तेमाल किया जाने वाला डिफ़ॉल्ट टेस्ट डिस्पैचर, UnconfinedTestDispatcher से बदलकर StandardTestDispatcher कर दिया गया है. इसका असर उन मामलों पर पड़ता है जहां कंस्ट्रक्टर या सबक्लास AndroidComposeUiTestEnvironment का इस्तेमाल करके कोई इंस्टेंस बनाया जाता है और उस कंस्ट्रक्टर को कॉल किया जाता है.

अहम बदलाव: कोरूटीन के एक्ज़ीक्यूशन पर असर

एपीआई के v1 और v2 के बीच मुख्य अंतर यह है कि कोरूटीन कैसे डिस्पैच किए जाते हैं:

  • v1 एपीआई (UnconfinedTestDispatcher): जब कोई कोरूटीन लॉन्च किया जाता था, तो वह मौजूदा थ्रेड पर तुरंत लागू हो जाता था. अक्सर, टेस्ट कोड की अगली लाइन चलने से पहले ही वह खत्म हो जाता था. प्रोडक्शन के व्यवहार के उलट, तुरंत लागू होने की इस सुविधा से, अनजाने में समय से जुड़ी असल समस्याएं या रेस कंडिशन छिप सकती हैं. ये समस्याएं, लाइव ऐप्लिकेशन में हो सकती हैं.
  • v2 एपीआई (StandardTestDispatcher): जब कोई कोरूटीन लॉन्च किया जाता है, तो उसे सूची में जोड़ दिया जाता है. साथ ही, उसे तब तक लागू नहीं किया जाता, जब तक टेस्ट साफ़ तौर पर वर्चुअल क्लॉक को आगे नहीं बढ़ाता. स्टैंडर्ड Compose टेस्ट एपीआई (जैसे कि waitForIdle()) पहले से ही इस सिंक्रनाइज़ेशन को मैनेज करते हैं. इसलिए, इन स्टैंडर्ड एपीआई पर निर्भर ज़्यादातर टेस्ट, बिना किसी बदलाव के काम करते रहेंगे.

आम तौर पर होने वाली गड़बड़ियां और उन्हें कैसे ठीक करें

अगर v2 पर अपग्रेड करने के बाद आपके टेस्ट फ़ेल हो जाते हैं, तो हो सकता है कि वे इस पैटर्न को दिखाएं:

  • फ़ेलियर: आपने कोई टास्क लॉन्च किया (उदाहरण के लिए, ViewModel डेटा लोड करता है), लेकिन आपका दावा तुरंत फ़ेल हो जाता है, क्योंकि डेटा अब भी "लोड हो रहा है" स्थिति में है.
  • वजह: v2 एपीआई के साथ, कोरूटीन तुरंत लागू होने के बजाय सूची में जोड़ दिए जाते हैं. टास्क को सूची में रखा गया था, लेकिन नतीजा देखने से पहले उसे कभी चलाया नहीं गया.
  • ठीक करने का तरीका: समय को साफ़ तौर पर आगे बढ़ाएं. आपको v2 डिस्पैचर को साफ़ तौर पर बताना होगा कि काम कब करना है.

पहले इस्तेमाल किया जाने वाला तरीका

v1 में, टास्क तुरंत शुरू और खत्म हो जाता था. वर्शन 2 में, यह कोड काम नहीं करता, क्योंकि loadData() अब तक नहीं चला है.

// In v1, this launched and finished immediately.
viewModel.loadData()

// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)

असर्शन से पहले, सूची में मौजूद टास्क को पूरा करने के लिए, waitForIdle या runOnIdle का इस्तेमाल करें.

पहला विकल्प: waitForIdle का इस्तेमाल करने से, घड़ी तब तक आगे बढ़ती है, जब तक यूज़र इंटरफ़ेस (यूआई) कुछ नहीं कर रहा होता. इससे यह पुष्टि होती है कि कोरूटीन चल रहा है.

viewModel.loadData()

// Explicitly run all queued tasks
composeTestRule.waitForIdle()

assertEquals(Success, viewModel.state.value)

दूसरा विकल्प: runOnIdle का इस्तेमाल करने पर, यूज़र इंटरफ़ेस (यूआई) के कुछ समय तक इस्तेमाल न होने के बाद, यूज़र इंटरफ़ेस (यूआई) थ्रेड पर कोड ब्लॉक को लागू किया जाता है.

viewModel.loadData()

// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
    assertEquals(Success, viewModel.state.value)
}

मैन्युअल तरीके से सिंक करना

मैन्युअल तरीके से सिंक्रनाइज़ेशन की स्थितियों में, जैसे कि एजेंडा के अगले विषय पर अपने-आप जाने की सुविधा बंद होने पर, कोरूटीन को लॉन्च करने से वह तुरंत लागू नहीं होता. ऐसा इसलिए होता है, क्योंकि टेस्ट क्लॉक को रोक दिया जाता है. वर्चुअल क्लॉक को आगे बढ़ाए बिना, सूची में मौजूद कोरूटीन को लागू करने के लिए, runCurrent() एपीआई का इस्तेमाल करें. यह मौजूदा वर्चुअल समय के लिए शेड्यूल किए गए टास्क चलाता है.

composeTestRule.mainClock.scheduler.runCurrent()

waitForIdle(), यूज़र इंटरफ़ेस (यूआई) के स्थिर होने तक टेस्ट क्लॉक को आगे बढ़ाता है. इसके उलट, runCurrent() मौजूदा वर्चुअल समय को बनाए रखते हुए, बाकी बचे टास्क को पूरा करता है. इस तरीके से, इंटरमीडिएट स्टेट की पुष्टि की जा सकती है. अगर क्लॉक को आइडल स्टेट पर सेट किया जाता है, तो इंटरमीडिएट स्टेट की पुष्टि नहीं की जा सकती.

टेस्ट एनवायरमेंट में इस्तेमाल किया जाने वाला टेस्ट शेड्यूलर एक्सपोज़ हो गया है. इस शेड्यूलर का इस्तेमाल, Kotlin runTest एपीआई के साथ किया जा सकता है, ताकि टेस्ट क्लॉक को सिंक किया जा सके.

runComposeUiTest पर माइग्रेट करें

अगर Kotlin runTest एपीआई के साथ Compose टेस्ट एपीआई का इस्तेमाल किया जा रहा है, तो हमारा सुझाव है कि आप runComposeUiTest पर स्विच करें.

पहले इस्तेमाल किया जाने वाला तरीका

createComposeRule के साथ runTest का इस्तेमाल करने पर, दो अलग-अलग क्लॉक बनती हैं: एक Compose के लिए और दूसरी टेस्ट कोरूटीन स्कोप के लिए. इस कॉन्फ़िगरेशन की वजह से, आपको टेस्ट शेड्यूलर को मैन्युअल तरीके से सिंक करना पड़ सकता है.

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testWithCoroutines() {
    composeTestRule.setContent {
        var status by remember { mutableStateOf("Loading...") }
        LaunchedEffect(Unit) {
            delay(1000)
            status = "Done!"
        }
        Text(text = status)
    }

    // NOT RECOMMENDED
    // Fails: runTest creates a new, separate scheduler.
    // Advancing time here does NOT advance the compose clock.
    // To fix this without migrating, you would need to share the scheduler
    // by passing 'composeTestRule.mainClock.scheduler' to runTest.
    runTest {
        composeTestRule.onNodeWithText("Loading...").assertIsDisplayed()
        advanceTimeBy(1000)
        composeTestRule.onNodeWithText("Done!").assertIsDisplayed()
    }
}

runComposeUiTest एपीआई, आपके टेस्ट ब्लॉक को अपने runTest स्कोप में अपने-आप लागू करता है. टेस्ट क्लॉक, Compose एनवायरमेंट के साथ सिंक होती है. इसलिए, आपको शेड्यूलर को मैन्युअल तरीके से मैनेज करने की ज़रूरत नहीं है.

    @Test
    fun testWithCoroutines() = runComposeUiTest {
        setContent {
            var status by remember { mutableStateOf("Loading...") }
            LaunchedEffect(Unit) {
                delay(1000)
                status = "Done!"
            }
            Text(text = status)
        }

        onNodeWithText("Loading...").assertIsDisplayed()
        mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */)
        onNodeWithText("Done!").assertIsDisplayed()
    }
}