coroutine का इस्तेमाल करने वाले यूनिट टेस्टिंग कोड पर ज़्यादा ध्यान देने की ज़रूरत होती है, क्योंकि उनका एक्ज़ीक्यूशन एसिंक्रोनस हो सकता है और कई थ्रेड में हो सकता है. इस गाइड में बताया गया है कि निलंबित करने वाले फ़ंक्शन की जांच कैसे की जा सकती है, टेस्टिंग कंस्ट्रक्शन से आपको जानकारी कैसे होनी चाहिए, और कोरूटीन इस्तेमाल करने वाले अपने कोड को टेस्ट करने लायक कैसे बनाया जा सकता है.
इस गाइड में इस्तेमाल किए गए एपीआई, kotlinx.coroutines.test लाइब्रेरी का हिस्सा हैं. इन एपीआई का ऐक्सेस पाने के लिए, अपने प्रोजेक्ट में टेस्ट डिपेंडेंसी के तौर पर आर्टफ़ैक्ट जोड़ना न भूलें.
dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
}
टेस्ट में सस्पेंडिंग फ़ंक्शन को शुरू करना
टेस्ट में सस्पेंडिंग फ़ंक्शन को कॉल करने के लिए, आपको कोरूटीन में होना चाहिए. JUnit टेस्ट फ़ंक्शन, फ़ंक्शन को निलंबित नहीं करते हैं, इसलिए नया कोरूटीन शुरू करने के लिए, आपको अपने टेस्ट में कोरूटीन बिल्डर को कॉल करना होगा.
runTest
एक कोरूटीन बिल्डर है, जिसे टेस्टिंग के लिए डिज़ाइन किया गया है. किसी भी ऐसे टेस्ट को रैप करने के लिए इसका इस्तेमाल करें जिसमें कोरूटीन शामिल हों. ध्यान दें कि कोरूटीन सीधे टेस्ट के मुख्य हिस्से में ही नहीं, बल्कि टेस्ट में इस्तेमाल किए जा रहे ऑब्जेक्ट से भी शुरू किए जा सकते हैं.
suspend fun fetchData(): String { delay(1000L) return "Hello world" } @Test fun dataShouldBeHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) }
आम तौर पर, हर टेस्ट में runTest
का इस्तेमाल करना चाहिए. हमारा सुझाव है कि आप एक्सप्रेशन के मुख्य हिस्से का इस्तेमाल करें.
टेस्ट के कोड को runTest
में रैप करने से, बेसिक निलंबित फ़ंक्शन की जांच की जा सकेगी. साथ ही, यह कोरूटीन में होने वाली किसी भी देरी को अपने-आप छोड़ देगा. इससे, ऊपर दिया गया टेस्ट एक सेकंड से भी ज़्यादा तेज़ी से पूरा हो जाता है.
हालांकि, टेस्ट के तहत आपके कोड में क्या होता है, इस आधार पर कुछ और चीज़ों पर ध्यान देना ज़रूरी है:
- जब आपका कोड,
runTest
से बनाए जाने वाले टॉप-लेवल टेस्ट कोरूटीन के अलावा नए कोरूटीन बनाता है, तो आपको सहीTestDispatcher
को चुनकर यह कंट्रोल करना होगा कि उन नए कोरूटीन को कैसे शेड्यूल किया जाए. - अगर आपका कोड, कोरोटिन को दूसरे डिस्पैचर पर ले जाता है (उदाहरण के लिए,
withContext
का इस्तेमाल करके), तो आम तौर परrunTest
काम करेगा. हालांकि, देरी नहीं होगी. साथ ही, एक से ज़्यादा थ्रेड पर कोड चलने की वजह से, जांच का अनुमान कम लगाया जा सकेगा. इन वजहों से, टेस्ट में आपको असली डिसपैचर की जगह, टेस्ट डिसपैचर इंजेक्ट करने चाहिए.
टेस्टडिस्पैचर
TestDispatchers
, CoroutineDispatcher
को टेस्ट करने के लिए लागू किया जाता है. अगर टेस्ट के दौरान नए कोरूटीन बनाए जाते हैं, तो आपको TestDispatchers
का इस्तेमाल करना होगा, ताकि नए कोरूटीन लागू किए जा सकें.
TestDispatcher
लागू करने के दो तरीके उपलब्ध हैं: StandardTestDispatcher
और UnconfinedTestDispatcher
, जो नए शुरू किए गए कोरूटीन को अलग-अलग शेड्यूल करते हैं. ये दोनों, वर्चुअल टाइम को कंट्रोल करने और टेस्ट में चल रहे कोरूटीन मैनेज करने के लिए, TestCoroutineScheduler
का इस्तेमाल करते हैं.
टेस्ट में सिर्फ़ एक शेड्यूलर इंस्टेंस का इस्तेमाल किया जाना चाहिए. इसे सभी TestDispatchers
के बीच शेयर किया जाता है. शेड्यूलर शेयर करने के बारे में जानने के लिए, TestDispatchers इंजेक्ट करना देखें.
टॉप-लेवल टेस्ट कोरूटीन शुरू करने के लिए, runTest
एक TestScope
बनाता है. यह CoroutineScope
को लागू करने का एक ऐसा तरीका है जिसमें हमेशा TestDispatcher
का इस्तेमाल किया जाएगा. अगर इसके लिए कोई वैल्यू नहीं दी गई है, तो TestScope
डिफ़ॉल्ट रूप से एक StandardTestDispatcher
बनाएगा. साथ ही, इसका इस्तेमाल टॉप-लेवल के टेस्ट कोरूटीन चलाने के लिए करेगा.
runTest
उन कोरूटीन को ट्रैक करता है जो अपने TestScope
के डिस्पैचर के ज़रिए इस्तेमाल किए गए शेड्यूलर पर लाइन में लगे हैं. साथ ही, यह तब तक नहीं लौटेगा, जब तक उस शेड्यूलर पर काम चल रहा है.
StandardTestDispatcher
जब किसी StandardTestDispatcher
पर नए कोरूटीन शुरू किए जाते हैं, तो उन्हें मौजूदा शेड्यूलर पर सूची में जोड़ दिया जाता है. ऐसा तब किया जाता है, जब टेस्ट थ्रेड बिना किसी शुल्क के इस्तेमाल की जा सकती है. इन नए कोरूटीन को चलाने के लिए, आपको टेस्ट थ्रेड को यील्ड करना होगा. अन्य कोरूटीन इस्तेमाल करने के लिए, इसे खाली करें. सूची बनाने की इस कार्रवाई से, आपको इस पर सटीक कंट्रोल मिलता है कि टेस्ट के दौरान नए कोरूटीन कैसे चलते हैं. साथ ही, यह प्रोडक्शन कोड में कोरूटीन शेड्यूल करने जैसा होता है.
अगर टॉप-लेवल टेस्ट कोरूटीन चलाने के दौरान टेस्ट थ्रेड कभी नहीं मिलता है, तो कोई भी नया कोरूटीन, टेस्ट कोरूटीन पूरा होने के बाद ही चलेगा. हालांकि, यह runTest
के वापस आने से पहले चलेगा:
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
सूची में मौजूद कोरूटीन चलाने के लिए, टेस्ट कोरूटीन पाने के कई तरीके हैं. ये सभी कॉल, लौटने से पहले अन्य कोरूटीन को टेस्ट थ्रेड पर चलने देते हैं:
advanceUntilIdle
: यह शेड्यूलर पर अन्य सभी कोरूटीन को तब तक चालू करता है, जब तक सूची में कुछ बचे नहीं. यह एक अच्छा डिफ़ॉल्ट विकल्प है, ताकि उन सभी कोरूटीन को चलाया जा सकता है जिन्हें मंज़ूरी मिलना बाकी है. साथ ही, यह टेस्ट की ज़्यादातर स्थितियों में भी काम करेगा.advanceTimeBy
: वर्चुअल टाइम को, दी गई रकम के हिसाब से बदलता है और उस पॉइंट से पहले चलने के लिए शेड्यूल किए गए कोरूटीन को वर्चुअल टाइम में चलाता है.runCurrent
: मौजूदा वर्चुअल समय पर शेड्यूल किए गए कोरूटीन रन करता है.
पिछले टेस्ट को ठीक करने के लिए, advanceUntilIdle
का इस्तेमाल किया जा सकता है, ताकि दावा जारी रखने से पहले, उन दो कोरूटीन को अपना काम करने दिया जा सके जिन्हें मंज़ूरी मिलना बाकी है:
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } advanceUntilIdle() // Yields to perform the registrations assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
सीमित जांच मात्रा
जब UnconfinedTestDispatcher
पर नए कोरूटीन शुरू होते हैं, तो वे मौजूदा थ्रेड पर तेज़ी से शुरू हो जाते हैं. इसका मतलब है कि वे अपने कोरूटीन बनाने वाले के वापस आने का इंतज़ार किए बिना, तुरंत चलना शुरू कर देंगे. कई मामलों में, इस डिस्पैच करने के तरीके की वजह से टेस्ट कोड आसान हो जाता है, क्योंकि आपको नए कोरूटीन चलाने के लिए, मैन्युअल तौर पर टेस्ट थ्रेड को जनरेट करने की ज़रूरत नहीं होती.
हालांकि, यह व्यवहार आपको प्रोडक्शन में गैर-जांच करने वाले लोगों के साथ अलग तरह से दिखेगा. अगर टेस्ट में कई चीज़ों को एक साथ इस्तेमाल करने पर फ़ोकस होता है, तो StandardTestDispatcher
का इस्तेमाल करें.
इस डिसपैचर का इस्तेमाल, डिफ़ॉल्ट कोरोइन के बजाय runTest
में टॉप-लेवल टेस्ट कोरूटीन के लिए किया जा सकता है. इसके लिए, एक इंस्टेंस बनाएं और उसे पैरामीटर के तौर पर पास करें. इससे runTest
में बनाए गए नए कोरूटीन तेज़ी से काम करेंगे, क्योंकि वे डिस्पैचर को TestScope
से इनहेरिट करते हैं.
@Test fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
इस उदाहरण में, लॉन्च कॉल अपने नए कोरूटीन को UnconfinedTestDispatcher
पर जल्द से जल्द शुरू करेंगे. इसका मतलब है कि लॉन्च किया जाने वाला हर कॉल, रजिस्ट्रेशन पूरा होने के बाद ही वापस आएगा.
याद रखें कि UnconfinedTestDispatcher
बहुत उत्सुकता से नए कोरूटीन शुरू करता है. हालांकि, इसका मतलब यह नहीं है कि वह उन्हें भी बहुत जल्द पूरा करने के लिए चलाएगा. अगर नए कोरूटीन निलंबित हो जाते हैं, तो दूसरे कोरूटीन फिर से काम करना शुरू कर देंगे.
उदाहरण के लिए, इस टेस्ट में लॉन्च किया गया नया कोरूटीन, ऐलिस को रजिस्टर करेगा. हालांकि, delay
को कॉल करने पर यह निलंबित हो जाएगा. यह टॉप-लेवल कोरूटीन को दावे के साथ आगे बढ़ने की अनुमति देता है. साथ ही, टेस्ट पूरा नहीं हो पाता, क्योंकि बॉब ने अभी तक रजिस्टर नहीं किया है:
@Test fun yieldingTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") delay(10L) userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
टेस्ट डिसपैचर इंजेक्ट किए जा रहे हैं
टेस्ट में कोड, थ्रेड बदलने (withContext
का इस्तेमाल करके) या नए कोरूटीन शुरू करने के लिए, डिस्पैचर का इस्तेमाल कर सकता है. जब कोड को साथ-साथ कई थ्रेड पर चलाया जाता है, तो टेस्ट में गड़बड़ी हो सकती है. सही समय पर दावे करना या बैकग्राउंड थ्रेड पर चल रहे टास्क के पूरे होने का इंतज़ार करना मुश्किल हो सकता है जिन पर आपका कोई कंट्रोल नहीं है.
टेस्ट में, इन डिस्पैचर को TestDispatchers
के इंस्टेंस से बदलें. इसके कई फ़ायदे हैं:
- यह कोड, सिंगल टेस्ट थ्रेड पर चलेगा. इससे टेस्ट ज़्यादा डिटरमिनिस्टिक होगा
- आपके पास यह कंट्रोल करने का विकल्प है कि नए कोरूटीन को शेड्यूल और एक्ज़ीक्यूट कैसे किया जाए
- टेस्ट डिस्पैचर, वर्चुअल टाइम के लिए शेड्यूलर का इस्तेमाल करते हैं. इसमें, देरी नहीं होती और आपको मैन्युअल तरीके से समय को आगे बढ़ाने की सुविधा मिलती है
डिपेंडेंसी इंजेक्शन का इस्तेमाल करके,
आपकी क्लास के ट्रांसपैचर की मदद से, असली डिसपैचर को बदलना आसान हो जाता है
टेस्ट. इन उदाहरणों में, हम CoroutineDispatcher
इंजेक्ट करेंगे, लेकिन यह भी किया जा सकता है
पूरी दुनिया को इंजेक्ट करें
CoroutineContext
टाइप किया गया है, जो टेस्ट के दौरान ज़्यादा विकल्प देता है.
कोरूटीन शुरू करने वाली क्लास के लिए, एक CoroutineScope
को इंजेक्ट किया जा सकता है
के बजाय, जैसा कि स्कोप इंजेक्ट करना में बताया गया है
सेक्शन में जाएं.
जब TestDispatchers
को इंस्टैंशिएट किया जाएगा, तो डिफ़ॉल्ट रूप से एक नया शेड्यूलर बन जाएगा. runTest
में, आप TestScope
की testScheduler
प्रॉपर्टी को ऐक्सेस कर सकते हैं और इसे किसी भी नए बनाए गए TestDispatchers
में पास कर सकते हैं. इससे वर्चुअल टाइम के बारे में उनकी समझ को शेयर किया जाएगा. साथ ही, advanceUntilIdle
जैसे तरीकों से सभी टेस्ट डिसपैचर को कोरूटीन पूरा होगा.
इस उदाहरण में, आपको ऐसी Repository
क्लास दिख सकती है जो IO
डिस्पैचर की initialize
तरीके में एक नया कोरूटीन बनाती है. साथ ही, यह कॉलर को उसकी fetchData
तरीके में IO
डिसपैचर पर स्विच करती है:
// Example class demonstrating dispatcher use cases class Repository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) val initialized = AtomicBoolean(false) // A function that starts a new coroutine on the IO dispatcher fun initialize() { scope.launch { initialized.set(true) } } // A suspending function that switches to the IO dispatcher suspend fun fetchData(): String = withContext(ioDispatcher) { require(initialized.get()) { "Repository should be initialized first" } delay(500L) "Hello world" } }
टेस्ट में, IO
डिसपैचर की जगह लेने के लिए, TestDispatcher
इंजेक्ट किया जा सकता है.
नीचे दिए गए उदाहरण में, हम डेटा स्टोर करने की जगह में StandardTestDispatcher
इंजेक्ट करते हैं और advanceUntilIdle
का इस्तेमाल करके यह पक्का करते हैं कि initialize
से शुरू हुआ नया कोरूटीन, आगे बढ़ने से पहले पूरा हो जाता है.
fetchData
को TestDispatcher
पर चलाने से भी फ़ायदा होगा. ऐसा इसलिए, क्योंकि यह टेस्ट थ्रेड पर चलेगा और जांच के दौरान होने वाली देरी को छोड़ देगा.
class RepositoryTest { @Test fun repoInitWorksAndDataIsHelloWorld() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = Repository(dispatcher) repository.initialize() advanceUntilIdle() // Runs the new coroutine assertEquals(true, repository.initialized.get()) val data = repository.fetchData() // No thread switch, delay is skipped assertEquals("Hello world", data) } }
TestDispatcher
पर शुरू किए गए नए कोरूटीन को मैन्युअल तरीके से बेहतर किया जा सकता है. इसके बारे में initialize
की मदद से दिखाया गया है. हालांकि, ध्यान दें कि प्रोडक्शन कोड में ऐसा किया जा सकता है या ऐसा किया जा सकता है. इसके बजाय, इस तरीके को फिर से डिज़ाइन करना चाहिए, ताकि यह निलंबित हो सके (एक क्रम में चलने पर एक्ज़ीक्यूशन के लिए) या Deferred
वैल्यू दिखा सके (एक साथ चल रहे एक्ज़ीक्यूशन के लिए).
उदाहरण के लिए, नया कोरूटीन शुरू करने और एक Deferred
बनाने के लिए, async
का इस्तेमाल किया जा सकता है:
class BetterRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) fun initialize() = scope.async { // ... } }
इससे, टेस्ट और प्रोडक्शन कोड, दोनों में इस कोड को सुरक्षित तरीके से await
किया जा सकता है:
@Test fun repoInitWorks() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = BetterRepository(dispatcher) repository.initialize().await() // Suspends until the new coroutine is done assertEquals(true, repository.initialized.get()) // ... }
अगर कोरूटीन किसी ऐसे TestDispatcher
पर हों जिसके साथ शेड्यूलर शेयर किया गया हो, तो runTest
कोरूटीन वापस आने से पहले उनके पूरा होने का इंतज़ार करती है. यह ऐसे कोरूटीन के लिए भी इंतज़ार करेगा जो टॉप-लेवल टेस्ट कोरूटीन के चाइल्ड हैं. भले ही, वे दूसरे डिसपैचर पर हों (डिफ़ॉल्ट रूप से 60 सेकंड, यानी कि dispatchTimeoutMs
पैरामीटर से तय किए गए टाइम आउट तक).
मुख्य डिसपैचर सेट करना
लोकल यूनिट टेस्ट में, Android यूज़र इंटरफ़ेस (यूआई) थ्रेड को रैप करने वाला Main
डिसपैचर उपलब्ध नहीं होगा, क्योंकि ये टेस्ट Android डिवाइस पर नहीं, बल्कि लोकल JVM पर किए जाते हैं. अगर जांच में शामिल आपके कोड में मुख्य थ्रेड का रेफ़रंस दिया गया है, तो यूनिट जांच के दौरान इसे अपवाद माना जाएगा.
कुछ मामलों में, पिछले सेक्शन में बताए गए तरीके की तरह, Main
डिसपैचर को इंजेक्ट किया जा सकता है, ताकि टेस्ट में इसे TestDispatcher
से बदला जा सके. हालांकि, viewModelScope
जैसे कुछ एपीआई, हुड के नीचे हार्डकोड Main
डिस्पैचर का इस्तेमाल करते हैं.
यहां ViewModel
को लागू करने का एक उदाहरण दिया गया है, जो डेटा लोड करने वाले कोरूटीन को लॉन्च करने के लिए, viewModelScope
का इस्तेमाल करता है:
class HomeViewModel : ViewModel() { private val _message = MutableStateFlow("") val message: StateFlow<String> get() = _message fun loadMessage() { viewModelScope.launch { _message.value = "Greetings!" } } }
सभी मामलों में, Main
डिसपैचर को TestDispatcher
से बदलने के लिए, Dispatchers.setMain
और Dispatchers.resetMain
फ़ंक्शन का इस्तेमाल करें.
class HomeViewModelTest { @Test fun settingMainDispatcher() = runTest { val testDispatcher = UnconfinedTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val viewModel = HomeViewModel() viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly assertEquals("Greetings!", viewModel.message.value) } finally { Dispatchers.resetMain() } } }
अगर Main
डिसपैचर को TestDispatcher
से बदल दिया जाता है, तो कोई भी नया बनाया गया TestDispatchers
, Main
डिस्पैचर से शेड्यूलर का अपने-आप इस्तेमाल करेगा. इसमें runTest
का बनाया गया StandardTestDispatcher
भी शामिल है. ऐसा तब होगा, जब इसको कोई और डिस्पैचर पास नहीं किया जाएगा.
इससे यह पक्का करना आसान हो जाता है कि टेस्ट के दौरान सिर्फ़ एक शेड्यूलर का इस्तेमाल किया जा रहा है. यह सुविधा काम करे, इसके लिए पक्का करें कि Dispatchers.setMain
को कॉल करने के बाद, अन्य TestDispatcher
इंस्टेंस बनाए जाएं.
हर जांच में Main
डिस्पैचर की जगह लेने वाले कोड का डुप्लीकेट बनाने से बचने का एक सामान्य पैटर्न, उसे JUnit टेस्ट नियम में एक्सट्रैक्ट करना है:
// Reusable JUnit4 TestRule to override the Main dispatcher class MainDispatcherRule( val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), ) : TestWatcher() { override fun starting(description: Description) { Dispatchers.setMain(testDispatcher) } override fun finished(description: Description) { Dispatchers.resetMain() } } class HomeViewModelTestUsingRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun settingMainDispatcher() = runTest { // Uses Main’s scheduler val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } }
यह नियम लागू करने के लिए, डिफ़ॉल्ट रूप से UnconfinedTestDispatcher
का इस्तेमाल किया जाता है. हालांकि, अगर Main
डिसपैचर को किसी टेस्ट क्लास में तेज़ी से कार्रवाई नहीं करनी है, तो StandardTestDispatcher
को पैरामीटर के तौर पर पास किया जा सकता है.
जब आपको जांच के मुख्य भाग में TestDispatcher
इंस्टेंस की ज़रूरत हो, तो नियम में मौजूद testDispatcher
का फिर से इस्तेमाल किया जा सकता है. बशर्ते, यह आपकी पसंद के मुताबिक हो. अगर आपको जांच में इस्तेमाल किए गए TestDispatcher
के टाइप के बारे में साफ़ तौर पर जानकारी देनी है या अगर आपको कोई ऐसा TestDispatcher
चाहिए जो Main
के लिए इस्तेमाल किए गए तरीके से अलग हो, तो runTest
में नया TestDispatcher
बनाएं. Main
डिस्पैचर TestDispatcher
पर सेट होने की वजह से, कोई भी नया बनाया गया TestDispatchers
अपने-आप शेड्यूलर शेयर करेगा.
class DispatcherTypesTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun injectingTestDispatchers() = runTest { // Uses Main’s scheduler // Use the UnconfinedTestDispatcher from the Main dispatcher val unconfinedRepo = Repository(mainDispatcherRule.testDispatcher) // Create a new StandardTestDispatcher (uses Main’s scheduler) val standardRepo = Repository(StandardTestDispatcher()) } }
परीक्षण के बाहर डिसपैचर बनाना
कुछ मामलों में, जांच के तरीके के बजाय किसी और तरीके से इस्तेमाल करने के लिए, आपको TestDispatcher
की ज़रूरत पड़ सकती है. उदाहरण के लिए, टेस्ट क्लास में किसी प्रॉपर्टी की शुरुआत के दौरान:
class ExampleRepository(private val ioDispatcher: CoroutineDispatcher) { /* ... */ } class RepositoryTestWithRule { private val repository = ExampleRepository(/* What TestDispatcher? */) @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun someRepositoryTest() = runTest { // Test the repository... // ... } }
अगर पिछले सेक्शन में दिखाए गए तरीके से Main
डिस्पैचर को बदला जा रहा है, तो Main
डिस्पैचर को बदलने के बाद बनाया गया TestDispatchers
, अपना शेड्यूलर अपने-आप शेयर कर देगा.
हालांकि, यह बात जांच क्लास की प्रॉपर्टी के तौर पर बनाई गई TestDispatchers
या टेस्ट क्लास में प्रॉपर्टी शुरू करने के दौरान बनाई गई TestDispatchers
के लिए नहीं होती है. Main
डिस्पैचर को बदलने से पहले, ये शुरू किए जाते हैं. इसलिए, वे नए शेड्यूलर बनाएंगे.
यह पक्का करने के लिए कि आपके टेस्ट में सिर्फ़ एक शेड्यूलर है, पहले MainDispatcherRule
प्रॉपर्टी बनाएं. फिर ज़रूरत के हिसाब से, अन्य क्लास-लेवल प्रॉपर्टी के इनीशियलाइज़र में इसके डिसपैचर (या इसके शेड्यूलर, अगर आपको किसी दूसरे टाइप का TestDispatcher
की ज़रूरत है) का फिर से इस्तेमाल करें.
class RepositoryTestWithRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() private val repository = ExampleRepository(mainDispatcherRule.testDispatcher) @Test fun someRepositoryTest() = runTest { // Takes scheduler from Main // Any TestDispatcher created here also takes the scheduler from Main val newTestDispatcher = StandardTestDispatcher() // Test the repository... } }
ध्यान दें कि जांच में बनाए गए runTest
और TestDispatchers
, अब भी Main
डिस्पैचर का शेड्यूलर अपने-आप शेयर करेंगे.
अगर आप Main
डिस्पैचर को नहीं बदल रहे हैं, तो क्लास की प्रॉपर्टी के रूप में अपना पहला TestDispatcher
(इससे एक नया शेड्यूलर बनता है) बनाएं. इसके बाद, उस शेड्यूलर को हर runTest
कॉल पर मैन्युअल तरीके से पास करें और बनाए गए हर नए TestDispatcher
को प्रॉपर्टी के तौर पर और टेस्ट में, दोनों तरीकों से पास करें:
class RepositoryTest { // Creates the single test scheduler private val testDispatcher = UnconfinedTestDispatcher() private val repository = ExampleRepository(testDispatcher) @Test fun someRepositoryTest() = runTest(testDispatcher.scheduler) { // Take the scheduler from the TestScope val newTestDispatcher = UnconfinedTestDispatcher(this.testScheduler) // Or take the scheduler from the first dispatcher, they’re the same val anotherTestDispatcher = UnconfinedTestDispatcher(testDispatcher.scheduler) // Test the repository... } }
इस नमूने में, पहले डिस्पैचर से शेड्यूलर runTest
को भेजा जाता है. ऐसा करने पर, उस शेड्यूलर का इस्तेमाल करके TestScope
के लिए एक नया StandardTestDispatcher
बन जाएगा. उस डिस्पैचर पर टेस्ट कोरूटीन चलाने के लिए, डिसपैचर को सीधे runTest
को भी पास किया जा सकता है.
अपना खुद का TestScope बनाना
TestDispatchers
की तरह ही, आपको जांच के मुख्य हिस्से के बाहर मौजूद TestScope
को ऐक्सेस करना पड़ सकता है. runTest
अपने-आप हुड के तहत TestScope
बनाता है. हालांकि, runTest
के साथ इस्तेमाल करने के लिए, अपना TestScope
भी बनाया जा सकता है.
ऐसा करते समय, अपने बनाए गए TestScope
पर runTest
को कॉल करना न भूलें:
class SimpleExampleTest { val testScope = TestScope() // Creates a StandardTestDispatcher @Test fun someTest() = testScope.runTest { // ... } }
ऊपर दिया गया कोड, सीधे तौर पर TestScope
के लिए एक StandardTestDispatcher
और एक नया शेड्यूलर बनाता है. इन सभी ऑब्जेक्ट को अलग से भी बनाया जा सकता है. अगर आपको इसे डिपेंडेंसी इंजेक्शन सेटअप के साथ इंटिग्रेट करना है, तो यह काफ़ी मददगार हो सकता है.
class ExampleTest { val testScheduler = TestCoroutineScheduler() val testDispatcher = StandardTestDispatcher(testScheduler) val testScope = TestScope(testDispatcher) @Test fun someTest() = testScope.runTest { // ... } }
स्कोप इंजेक्ट करना
अगर आपके पास कोई ऐसी क्लास है जो कोरूटीन बनाती है, तो आपको
टेस्ट करते हैं, तो उस क्लास में कोरूटीन स्कोप इंजेक्ट किया जा सकता है. इसकी जगह
टेस्ट में TestScope
.
इस उदाहरण में, UserState
क्लास UserRepository
पर निर्भर है
का इस्तेमाल, नए उपयोगकर्ताओं को रजिस्टर करने और रजिस्टर किए गए उपयोगकर्ताओं की सूची फ़ेच करने के लिए किया जाता है. इन कॉल में
से UserRepository
फ़ंक्शन कॉल निलंबित कर रहे हैं, UserState
इंजेक्ट किए गए का इस्तेमाल करता है
CoroutineScope
अपने registerUser
फ़ंक्शन में नया कोरूटीन शुरू करने के लिए.
class UserState( private val userRepository: UserRepository, private val scope: CoroutineScope, ) { private val _users = MutableStateFlow(emptyList<String>()) val users: StateFlow<List<String>> = _users.asStateFlow() fun registerUser(name: String) { scope.launch { userRepository.register(name) _users.update { userRepository.getAllUsers() } } } }
इस क्लास की जांच करने के लिए, TestScope
में runTest
से पास करें
UserState
ऑब्जेक्ट:
class UserStateTest { @Test fun addUserTest() = runTest { // this: TestScope val repository = FakeUserRepository() val userState = UserState(repository, scope = this) userState.registerUser("Mona") advanceUntilIdle() // Let the coroutine complete and changes propagate assertEquals(listOf("Mona"), userState.users.value) } }
परीक्षण फ़ंक्शन के बाहर का स्कोप इंजेक्ट करने के लिए, उदाहरण के लिए नीचे मौजूद किसी ऑब्जेक्ट में एक जांच होती है, जिसे टेस्ट क्लास में प्रॉपर्टी के तौर पर बनाया जाता है, तो अपना TestScope बनाना.