Compose के टेस्टिंग एपीआई (createComposeRule,
createAndroidComposeRule, runComposeUiTest,
runAndroidComposeUiTest वगैरह) के v2 वर्शन अब उपलब्ध हैं. इनसे को-रूटीन के एक्ज़ीक्यूशन को बेहतर तरीके से कंट्रोल किया जा सकता है. इस अपडेट से, पूरे एपीआई को डुप्लीकेट नहीं किया जाता. सिर्फ़ उन एपीआई को अपडेट किया जाता है जो टेस्ट एनवायरमेंट बनाते हैं.
एपीआई के v1 वर्शन को बंद कर दिया गया है. हमारा सुझाव है कि आप नए एपीआई पर माइग्रेट करें. माइग्रेट करने से यह पुष्टि होती है कि आपके टेस्ट, स्टैंडर्ड को-रूटीन के व्यवहार के मुताबिक हैं. साथ ही, इससे आने वाले समय में कंपैटिबिलिटी से जुड़ी समस्याएं नहीं होती हैं. पाबंदी वाले v1 एपीआई की सूची देखने के लिए, एपीआई मैपिंग देखें.
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 testing v2 API का हिस्सा है. यह डिफ़ॉल्ट रूप से StandardTestDispatcher का इस्तेमाल करता है. अगर आपकी जांचों में v1 एपीआई का इस्तेमाल किया जाता है, तो ComposeUiTestConfig को अपनाने से पहले, v2 टेस्टिंग एपीआई पर माइग्रेट करना लेख पढ़ें.
ComposeUiTestConfig पर माइग्रेट करना
टेस्ट में सेटअप फ़ंक्शन बनाने के लिए, कई ओवरलोड उपलब्ध होते हैं. इनमें से कुछ ओवरलोड ऐसे होते हैं जो अलग-अलग कॉन्फ़िगरेशन पैरामीटर स्वीकार करते हैं. जैसे, effectContext, runTestContext या testTimeout. इन ओवरलोड को अब इस्तेमाल नहीं किया जा सकता. अपने टेस्ट अपडेट करें, ताकि ComposeUiTestConfig का इस्तेमाल किया जा सके. इसके लिए, यहां दिया गया उदाहरण देखें:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
डिफ़ॉल्ट इनपुट मोड
अगर टेस्ट, टेस्ट शुरू होने से पहले इंस्ट्रुमेंटेशन एपीआई के ज़रिए कॉन्फ़िगर किए गए नॉन-टच इनपुट मोड पर निर्भर करते हैं, तो माइग्रेशन के दौरान टेस्ट फ़ेल हो सकते हैं. टेस्ट के लिए सेटअप फ़ंक्शन में, सिस्टम हर टेस्ट की शुरुआत में डिफ़ॉल्ट रूप से 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 API पर अपग्रेड करते समय, आम तौर पर ढूंढें और बदलें सुविधा का इस्तेमाल करके, पैकेज इंपोर्ट को अपडेट किया जा सकता है. साथ ही, डिस्पैचर में हुए नए बदलावों को अपनाया जा सकता है.
इसके अलावा, Gemini से Compose टेस्टिंग एपीआई के v2 पर माइग्रेट करने के लिए कहा जा सकता है. इसके लिए, यह प्रॉम्प्ट इस्तेमाल करें:
एआई प्रॉम्प्ट
टेस्टिंग के लिए इस्तेमाल किए जाने वाले v1 एपीआई से टेस्टिंग के लिए इस्तेमाल किए जाने वाले v2 एपीआई पर माइग्रेट करना
यह प्रॉम्प्ट, v2 टेस्टिंग एपीआई पर माइग्रेट करने के लिए इस गाइड का इस्तेमाल करेगा.
Migrate to Compose testing v2 APIs using the official
migration guide.सेवा में नहीं रहे v1 एपीआई को उनके v2 वर्शन के साथ मैप करने के लिए, यहां दी गई टेबल का इस्तेमाल करें:
अब काम नहीं करता (v1) |
बदलाव (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
पुराने सिस्टम के साथ काम करने की सुविधा और अपवाद
मौजूदा v1 एपीआई अब काम नहीं करते. हालांकि, मौजूदा व्यवहार को बनाए रखने और बड़े बदलावों को रोकने के लिए, UnconfinedTestDispatcher का इस्तेमाल जारी रखें.
यहां सिर्फ़ एक ऐसा अपवाद दिया गया है जहां डिफ़ॉल्ट सेटिंग में बदलाव किया गया है:
AndroidComposeUiTestEnvironment क्लास में कंपोज़िशन चलाने के लिए इस्तेमाल किया जाने वाला डिफ़ॉल्ट टेस्ट डिस्पैचर, UnconfinedTestDispatcher से बदलकर StandardTestDispatcher हो गया है. इसका असर उन मामलों पर पड़ता है जहां कंस्ट्रक्टर का इस्तेमाल करके कोई इंस्टेंस बनाया जाता है या AndroidComposeUiTestEnvironment को सबक्लास किया जाता है और उस कंस्ट्रक्टर को कॉल किया जाता है.
मुख्य बदलाव: कोरूटीन के एक्ज़ीक्यूशन पर असर
एपीआई के v1 और v2 के बीच मुख्य अंतर यह है कि कोरूटीन को कैसे भेजा जाता है:
- v1 एपीआई (
UnconfinedTestDispatcher): जब कोई कोरूटीन लॉन्च किया जाता था, तो वह मौजूदा थ्रेड पर तुरंत एक्ज़ीक्यूट हो जाता था. साथ ही, अक्सर टेस्ट कोड की अगली लाइन के चलने से पहले ही पूरा हो जाता था. प्रोडक्शन के उलट, इस तुरंत लागू होने वाली सुविधा से अनजाने में, समय से जुड़ी असली समस्याएं या रेस की स्थितियां छिप सकती हैं. ये समस्याएं, लाइव ऐप्लिकेशन में होती हैं. - v2 एपीआई (
StandardTestDispatcher): जब कोई कोराउटीन लॉन्च किया जाता है, तो उसे कतार में रखा जाता है. यह तब तक काम नहीं करता, जब तक टेस्ट में वर्चुअल क्लॉक को साफ़ तौर पर आगे नहीं बढ़ाया जाता. स्टैंडर्ड कंपोज़ टेस्ट एपीआई (जैसे,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() } }