Skip to content

Commit fb83fad

Browse files
committed
add SearchVMTest.kt
1 parent bd7c5c9 commit fb83fad

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

feature-search/src/test/java/com/hoc/flowmvi/ui/search/SearchVMTest.kt

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.hoc081098.flowext.concatWith
1212
import com.hoc081098.flowext.timer
1313
import io.mockk.coEvery
1414
import io.mockk.coVerify
15+
import io.mockk.coVerifySequence
1516
import io.mockk.confirmVerified
1617
import io.mockk.mockk
1718
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -226,6 +227,90 @@ class SearchVMTest : BaseMviViewModelTest<ViewIntent, ViewState, SingleEvent, Se
226227
}
227228
}
228229

230+
@Test
231+
fun test_withSearchIntent_debounceSearchQueryThenRejectBlankThenDistinctUntilChangedAndCancelledPreviousExecution() {
232+
val query1 = "#query#1"
233+
val query2 = "#query#2"
234+
coEvery { searchUsersUseCase(query1) } coAnswers {
235+
repeat(10) { timeout() } // (1) very long... -> cancelled by (2)
236+
USERS.right()
237+
}
238+
coEvery { searchUsersUseCase(query2) } returns USERS.right()
239+
240+
test(
241+
vmProducer = { vm },
242+
intents = flowOf("a", "b", "c", query1)
243+
.map { ViewIntent.Search(it) }
244+
.onEach { delay(SEMI_TIMEOUT) }
245+
.onCompletion { timeout() }
246+
.concatWith(
247+
timer(
248+
ViewIntent.Search(query2),
249+
TOTAL_TIMEOUT,
250+
).onCompletion { timeout() }, // (2)
251+
),
252+
expectedStates = listOf(
253+
ViewState.initial(null),
254+
ViewState(
255+
users = emptyList(),
256+
isLoading = false,
257+
error = null,
258+
submittedQuery = "",
259+
originalQuery = "a", // update originalQuery
260+
),
261+
ViewState(
262+
users = emptyList(),
263+
isLoading = false,
264+
error = null,
265+
submittedQuery = "",
266+
originalQuery = "b", // update originalQuery
267+
),
268+
ViewState(
269+
users = emptyList(),
270+
isLoading = false,
271+
error = null,
272+
submittedQuery = "",
273+
originalQuery = "c", // update originalQuery
274+
),
275+
ViewState(
276+
users = emptyList(),
277+
isLoading = false,
278+
error = null,
279+
submittedQuery = "",
280+
originalQuery = query1, // update originalQuery
281+
),
282+
ViewState(
283+
users = emptyList(),
284+
isLoading = true, // update isLoading
285+
error = null,
286+
submittedQuery = "",
287+
originalQuery = query1,
288+
),
289+
ViewState(
290+
users = emptyList(),
291+
isLoading = true,
292+
error = null,
293+
submittedQuery = "",
294+
originalQuery = query2, // update originalQuery
295+
),
296+
ViewState(
297+
users = USER_ITEMS, // update users
298+
isLoading = false, // update isLoading
299+
error = null,
300+
submittedQuery = query2, // update submittedQuery
301+
originalQuery = query2,
302+
),
303+
).mapRight(),
304+
expectedEvents = emptyList(),
305+
delayAfterDispatchingIntents = EXTRAS_TIMEOUT,
306+
) {
307+
coVerifySequence {
308+
searchUsersUseCase(query1)
309+
searchUsersUseCase(query2)
310+
}
311+
}
312+
}
313+
229314
@Test
230315
fun test_withSearchIntent_returnsUserItemsWithProperLoadingState() {
231316
val query = "query"
@@ -322,6 +407,7 @@ class SearchVMTest : BaseMviViewModelTest<ViewIntent, ViewState, SingleEvent, Se
322407
ViewState.initial(null),
323408
).mapRight(),
324409
expectedEvents = emptyList(),
410+
delayAfterDispatchingIntents = EXTRAS_TIMEOUT,
325411
)
326412
}
327413

@@ -365,6 +451,7 @@ class SearchVMTest : BaseMviViewModelTest<ViewIntent, ViewState, SingleEvent, Se
365451
emit(ViewIntent.Search(query))
366452
timeout()
367453
},
454+
delayAfterDispatchingIntents = EXTRAS_TIMEOUT,
368455
) {
369456
coVerify(exactly = 2) { searchUsersUseCase(query) }
370457
}
@@ -409,11 +496,85 @@ class SearchVMTest : BaseMviViewModelTest<ViewIntent, ViewState, SingleEvent, Se
409496
emit(ViewIntent.Search(query))
410497
timeout()
411498
},
499+
delayAfterDispatchingIntents = EXTRAS_TIMEOUT,
412500
) {
413501
coVerify(exactly = 2) { searchUsersUseCase(query) }
414502
}
415503
}
416504

505+
@Test
506+
fun test_withRetryIntentWhenError_cancelledBySearchIntent() {
507+
val query1 = "#hoc081098#1"
508+
val query2 = "#hoc081098#2"
509+
val networkError = UserError.NetworkError
510+
511+
var count = 0
512+
coEvery { searchUsersUseCase(query1) } coAnswers {
513+
when (count++) {
514+
0 -> networkError.left()
515+
1 -> {
516+
repeat(3) { timeout() } // (1) very long ... -> cancelled by (2)
517+
USERS.right()
518+
}
519+
else -> error("Should not reach here!")
520+
}
521+
}
522+
coEvery { searchUsersUseCase(query2) } returns USERS.right()
523+
524+
test(
525+
vmProducer = { vm },
526+
intents = flowOf(ViewIntent.Retry).concatWith(
527+
flow {
528+
delay(SEMI_TIMEOUT) // (2) very short ...
529+
emit(ViewIntent.Search(query2))
530+
timeout()
531+
}
532+
),
533+
expectedStates = listOf(
534+
ViewState(
535+
users = emptyList(),
536+
isLoading = false,
537+
error = networkError,
538+
submittedQuery = query1,
539+
originalQuery = query1,
540+
),
541+
ViewState(
542+
users = emptyList(),
543+
isLoading = true, // update isLoading
544+
error = null, // update error
545+
submittedQuery = query1,
546+
originalQuery = query1,
547+
),
548+
ViewState(
549+
users = emptyList(),
550+
isLoading = true,
551+
error = null,
552+
submittedQuery = query1,
553+
originalQuery = query2, // update originalQuery
554+
),
555+
ViewState(
556+
users = USER_ITEMS, // update users
557+
isLoading = false, // update isLoading
558+
error = null,
559+
submittedQuery = query2, // update submittedQuery
560+
originalQuery = query2,
561+
),
562+
).mapRight(),
563+
expectedEvents = emptyList(),
564+
intentsBeforeCollecting = flow {
565+
emit(ViewIntent.Search(query1))
566+
timeout()
567+
},
568+
delayAfterDispatchingIntents = EXTRAS_TIMEOUT
569+
) {
570+
coVerifySequence {
571+
searchUsersUseCase(query1)
572+
searchUsersUseCase(query1)
573+
searchUsersUseCase(query2)
574+
}
575+
}
576+
}
577+
417578
private companion object {
418579
private val EXTRAS_TIMEOUT = Duration.milliseconds(100)
419580
private val TOTAL_TIMEOUT = SEARCH_DEBOUNCE_DURATION + EXTRAS_TIMEOUT

0 commit comments

Comments
 (0)