flow के साथ कम्यूनिकेशन करने वाली इकाइयों या मॉड्यूल की जांच करने का तरीका यह इस बात पर निर्भर करता है कि जिस विषय की जांच की जा रही है वह फ़्लो का इस्तेमाल इनपुट के तौर पर कर रहा है या आउटपुट के तौर पर.
- अगर जांच में शामिल व्यक्ति को कोई फ़्लो मिलता है, तो नकली डिपेंडेंसी जिन्हें टेस्ट से कंट्रोल किया जा सकता है.
- अगर यूनिट या मॉड्यूल से कोई फ़्लो दिखता है, तो उसे पढ़ा जा सकता है और उसकी पुष्टि की जा सकती है या एक से ज़्यादा आइटम की जांच करता है.
नकली प्रोड्यूसर बनाना
जब जांच के दायरे में आने वाला विषय, किसी फ़्लो का उपभोक्ता हो, तो उसकी जांच करने का एक सामान्य तरीका है प्रोड्यूसर की जगह एक नकली तरीका इस्तेमाल किया जा सकता है. उदाहरण के लिए, दिए गए क्लास, जो डेटा स्टोर करने की ऐसी जगह पर नज़र रखती है जो दो डेटा सोर्स से डेटा लेती है प्रोडक्शन:
टेस्ट को डिटरमिनिस्टिक बनाने के लिए, रिपॉज़िटरी और इसकी डेटा स्टोर करने की जगह पर उन डिपेंडेंसी का इस्तेमाल किया जाता है जो हमेशा एक जैसे नकली डेटा का उत्सर्जन करती हैं:
किसी फ़्लो में, पहले से तय की गई वैल्यू की सीरीज़ दिखाने के लिए, flow
बिल्डर का इस्तेमाल करें:
class MyFakeRepository : MyRepository {
fun observeCount() = flow {
emit(ITEM_1)
}
}
इस जांच में, डेटा स्टोर करने के लिए बनाए गए इस नकली डेटा को लागू करना:
@Test
fun myTest() {
// Given a class with fake dependencies:
val sut = MyUnitUnderTest(MyFakeRepository())
// Trigger and verify
...
}
अब आपके पास जिस विषय की जांच की जा रही है उसके आउटपुट को कंट्रोल करने की सुविधा है. इसलिए, ऐसा किया जा सकता है इसके आउटपुट की जाँच करके पुष्टि करें कि यह ठीक से काम कर रहा है.
टेस्ट में फ़्लो से होने वाले उत्सर्जन का दावा करना
जिस विषय की जांच की जा रही है, अगर उससे कोई फ़र्क़ दिख रहा है, तो जांच के लिए दावे करना ज़रूरी है डेटा स्ट्रीम के एलिमेंट पर काफ़ी बारीकी से काम करता है.
मान लें कि पिछले उदाहरण में, डेटा स्टोर करने की जगह में किसी फ़्लो को दिखाया गया है:
कुछ टेस्ट में, आपको सिर्फ़ पहले उत्सर्जन या तय सीमा की जांच करनी होगी की संख्या डालें.
first()
पर कॉल करके, पहली बार उत्सर्जन का डेटा निकाला जा सकता है. यह
फ़ंक्शन पहला आइटम मिलने तक इंतज़ार करता है और फिर रद्द करने की प्रक्रिया भेजता है
एक सिग्नल की तरह काम करता है.
@Test
fun myRepositoryTest() = runTest {
// Given a repository that combines values from two data sources:
val repository = MyRepository(fakeSource1, fakeSource2)
// When the repository emits a value
val firstItem = repository.counter.first() // Returns the first item in the flow
// Then check it's the expected item
assertEquals(ITEM_1, firstItem)
}
अगर जांच में कई वैल्यू की जांच करना ज़रूरी है, तो toList()
को कॉल करने से फ़्लो होता है
सोर्स को अपने सभी वैल्यू को दिखाने का इंतज़ार करता है. इसके बाद, उन वैल्यू को
सूची. यह सुविधा सिर्फ़ सीमित डेटा स्ट्रीम के लिए काम करती है.
@Test
fun myRepositoryTest() = runTest {
// Given a repository with a fake data source that emits ALL_MESSAGES
val messages = repository.observeChatMessages().toList()
// When all messages are emitted then they should be ALL_MESSAGES
assertEquals(ALL_MESSAGES, messages)
}
उन डेटा स्ट्रीम के लिए जिनमें आइटम के ज़्यादा जटिल कलेक्शन की ज़रूरत होती है या जो नहीं दिखते
आइटम की संख्या सीमित होती है. इन्हें चुनने और उनमें बदलाव करने के लिए, Flow
एपीआई का इस्तेमाल किया जा सकता है
आइटम. यहां कुछ उदाहरण दिए गए हैं:
// Take the second item
outputFlow.drop(1).first()
// Take the first 5 items
outputFlow.take(5).toList()
// Takes the first item verifying that the flow is closed after that
outputFlow.single()
// Finite data streams
// Verify that the flow emits exactly N elements (optional predicate)
outputFlow.count()
outputFlow.count(predicate)
जांच के दौरान लगातार डेटा इकट्ठा करना
पिछले उदाहरण में बताए गए तरीके से, toList()
का इस्तेमाल करके फ़्लो को इकट्ठा करना
collect()
, अंदरूनी तौर पर प्रोसेस करता है और नतीजों की पूरी सूची तैयार होने तक निलंबित करता है
वापस किया गया.
उन कार्रवाइयों को एक-दूसरे के साथ छोड़ने के लिए जिनकी वजह से, फ़्लो को ये वैल्यू उत्सर्जित की गई थीं, इसलिए आपको फ़्लो के दौरान लगातार वैल्यू इकट्ठा करने की सुविधा दी जा सकती है. एक टेस्ट.
उदाहरण के लिए, जांच करने के लिए, यहां दी गई Repository
क्लास लें और
नकली डेटा सोर्स को लागू किया गया है, जिसमें emit
तरीके से
जांच के दौरान डाइनैमिक तौर पर वैल्यू दें:
class Repository(private val dataSource: DataSource) {
fun scores(): Flow<Int> {
return dataSource.counts().map { it * 10 }
}
}
class FakeDataSource : DataSource {
private val flow = MutableSharedFlow<Int>()
suspend fun emit(value: Int) = flow.emit(value)
override fun counts(): Flow<Int> = flow
}
टेस्ट में इस नकली प्रॉडक्ट का इस्तेमाल करते समय, एक ऐसा कलेक्टिंग कोरूटीन बनाया जा सकता है जो
Repository
से लगातार वैल्यू पाएं. इस उदाहरण में, हम
सूची में शामिल करना और इसके कॉन्टेंट पर दावे करना:
@Test
fun continuouslyCollect() = runTest {
val dataSource = FakeDataSource()
val repository = Repository(dataSource)
val values = mutableListOf<Int>()
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
repository.scores().toList(values)
}
dataSource.emit(1)
assertEquals(10, values[0]) // Assert on the list contents
dataSource.emit(2)
dataSource.emit(3)
assertEquals(30, values[2])
assertEquals(3, values.size) // Assert the number of items collected
}
यहां Repository
से दिखाया गया फ़्लो कभी पूरा नहीं होता, इसलिए toList
कॉल से जुड़ा जा सकता है. यह कॉल कभी वापस नहीं आता. इतने समय में कोरूटीन इकट्ठा करना शुरू किया जा रहा है
TestScope.backgroundScope
यह पक्का करता है कि टेस्ट खत्म होने से पहले कोरूटीन रद्द हो जाए. या फिर,
runTest
, टेस्ट पूरा होने तक इंतज़ार करेगा, जिससे टेस्ट रुक जाएगा
और अंत में विफल हो जाता है.
ध्यान दें कि कैसे
UnconfinedTestDispatcher
का इस्तेमाल कोरूटीन इकट्ठा करने के लिए किया जाता है. इससे यह पक्का होता है कि
कोरूटीन को उत्सुकता से लॉन्च किया जाता है और यह launch
के बाद वैल्यू पाने के लिए तैयार है
वापस करना.
टर्बाइन का इस्तेमाल करना
तीसरे पक्ष की टर्बाइन लाइब्रेरी, कलेक्टिंग कोरूटीन बनाने के लिए एक सुविधाजनक एपीआई देती है. साथ ही, फ़्लो की टेस्टिंग की दूसरी सुविधा के तौर पर:
@Test
fun usingTurbine() = runTest {
val dataSource = FakeDataSource()
val repository = Repository(dataSource)
repository.scores().test {
// Make calls that will trigger value changes only within test{}
dataSource.emit(1)
assertEquals(10, awaitItem())
dataSource.emit(2)
awaitItem() // Ignore items if needed, can also use skip(n)
dataSource.emit(3)
assertEquals(30, awaitItem())
}
}
ज़्यादा जानकारी के लिए, लाइब्रेरी का दस्तावेज़ देखें.
स्टेट फ़्लो टेस्ट करना
StateFlow
एक मॉनिटर किया जा सकने वाला मॉडल है
डेटा होल्डर, जिसे समय के साथ इसकी वैल्यू देखने के लिए इकट्ठा किया जा सकता है
स्ट्रीम. ध्यान दें कि वैल्यू की यह स्ट्रीम आपस में मिल गई है. इसका मतलब है कि अगर
वैल्यू, StateFlow
में तेज़ी से सेट होती हैं. StateFlow
के कलेक्टर नहीं हैं
इस बात की गारंटी है कि आपको सभी इंटरमीडिएट वैल्यू मिलेंगी, सिर्फ़ सबसे नई वैल्यू मिलेंगी.
टेस्ट में, अगर आपको भ्रम की स्थिति को ध्यान में रखना है, तो StateFlow
की वैल्यू इकट्ठा की जा सकती है
जैसे कि टर्बाइन के साथ-साथ किसी भी अन्य फ़्लो को इकट्ठा किया जा सकता है. जानकारी इकट्ठा करने की कोशिश की जा रही है
और कुछ टेस्ट में सभी इंटरमीडिएट वैल्यू पर दावा करना सही हो सकता है.
हालांकि, हम आम तौर पर StateFlow
को डेटा होल्डर के तौर पर इस्तेमाल करने और
और अपनी value
प्रॉपर्टी पर दावा करना. इस तरह, टेस्ट
किसी दिए गए समय पर ऑब्जेक्ट की अवस्था होती है और इस बात पर निर्भर नहीं रहती कि
एक-दूसरे में उलझन हो जाती है.
उदाहरण के लिए, इस ViewModel को लें, जो Repository
से वैल्यू इकट्ठा करता है और
उन्हें यूज़र इंटरफ़ेस (यूआई) में StateFlow
में दिखाता है:
class MyViewModel(private val myRepository: MyRepository) : ViewModel() {
private val _score = MutableStateFlow(0)
val score: StateFlow<Int> = _score.asStateFlow()
fun initialize() {
viewModelScope.launch {
myRepository.scores().collect { score ->
_score.value = score
}
}
}
}
इस Repository
को लागू करने का एक नकली तरीका ऐसा दिख सकता है:
class FakeRepository : MyRepository {
private val flow = MutableSharedFlow<Int>()
suspend fun emit(value: Int) = flow.emit(value)
override fun scores(): Flow<Int> = flow
}
इस नकली मॉडल के साथ ViewModel की जांच करते समय, नकली वैल्यू से
ViewModel के StateFlow
में अपडेट ट्रिगर करता है और फिर अपडेट किए गए
value
:
@Test
fun testHotFakeRepository() = runTest {
val fakeRepository = FakeRepository()
val viewModel = MyViewModel(fakeRepository)
assertEquals(0, viewModel.score.value) // Assert on the initial value
// Start collecting values from the Repository
viewModel.initialize()
// Then we can send in values one by one, which the ViewModel will collect
fakeRepository.emit(1)
assertEquals(1, viewModel.score.value)
fakeRepository.emit(2)
fakeRepository.emit(3)
assertEquals(3, viewModel.score.value) // Assert on the latest value
}
StateIn के बनाए गए StateFlows के साथ काम करना
पिछले सेक्शन में, ViewModel,MutableStateFlow
Repository
के फ़्लो से जनरेट की गई नई वैल्यू. यह एक सामान्य पैटर्न है.
आम तौर पर,
stateIn
ऑपरेटर, जो कोल्ड फ़्लो को हॉट StateFlow
में बदलता है:
class MyViewModelWithStateIn(myRepository: MyRepository) : ViewModel() {
val score: StateFlow<Int> = myRepository.scores()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 0)
}
stateIn
ऑपरेटर में SharingStarted
पैरामीटर होता है. इससे तय होता है कि
यह ऐक्टिव हो जाता है और दिए गए फ़्लो का इस्तेमाल करने लगता है. ऐसे विकल्प
SharingStarted.Lazily
और SharingStarted.WhileSubsribed
अक्सर इस्तेमाल किए जाते हैं
ViewModels में.
भले ही, आप अपने टेस्ट में StateFlow
के value
पर दावा कर रहे हों, तब भी आपको
कलेक्टर बनाना होगा. यह कलेक्टर खाली हो सकता है:
@Test
fun testLazilySharingViewModel() = runTest {
val fakeRepository = HotFakeRepository()
val viewModel = MyViewModelWithStateIn(fakeRepository)
// Create an empty collector for the StateFlow
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
viewModel.score.collect()
}
assertEquals(0, viewModel.score.value) // Can assert initial value
// Trigger-assert like before
fakeRepository.emit(1)
assertEquals(1, viewModel.score.value)
fakeRepository.emit(2)
fakeRepository.emit(3)
assertEquals(3, viewModel.score.value)
}
अन्य संसाधन
- Android पर Kotlin कोरूटीन की जांच करना
- Kotlin की सुविधा Android पर उपलब्ध है
StateFlow
औरSharedFlow
- Kotlin कोरूटीन और फ़्लो के लिए अतिरिक्त संसाधन