Compose test v2 API

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

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

ये बदलाव androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ और androidx.compose.ui:ui-test:1.11.0-alpha03+ में शामिल हैं.

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

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

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

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

ComposeUiTestConfig, Compose Test 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
    }
}

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

टेस्टिंग के लिए उपलब्ध एपीआई के वर्शन 2 पर माइग्रेट करना

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

इसके अलावा, 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 डिस्पैचर को साफ़ तौर पर बताना होगा कि काम कब करना है.

पिछला तरीका

पहले वर्शन में, टास्क तुरंत शुरू हो जाता था और तुरंत खत्म हो जाता था. वर्शन 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 API के साथ-साथ Compose test API का इस्तेमाल किया जा रहा है, तो हमारा सुझाव है कि आप runComposeUiTest पर स्विच करें.

पिछला तरीका

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

@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 स्कोप में आपके टेस्ट ब्लॉक को अपने-आप लागू करता है. टेस्ट क्लॉक को कंपोज़ एनवायरमेंट के साथ सिंक किया जाता है, इसलिए अब आपको शेड्यूल करने वाले टूल को मैन्युअल तरीके से मैनेज करने की ज़रूरत नहीं है.

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