Skip to content

Commit f2917ca

Browse files
committed
add SearchVMTest.kt
1 parent f9c1f65 commit f2917ca

File tree

5 files changed

+157
-14
lines changed

5 files changed

+157
-14
lines changed

feature-search/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ dependencies {
6565
implementation(deps.timber)
6666

6767
addUnitTest()
68+
testImplementation(mviTesting)
6869
}

feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchVM.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class SearchVM(
7676
.shareWhileSubscribed()
7777

7878
val searchableQueryFlow = queryFlow
79-
.debounce(Duration.milliseconds(400))
79+
.debounce(SEARCH_DEBOUNCE_DURATION)
8080
.filter { it.isNotBlank() }
8181
.distinctUntilChanged()
8282
.shareWhileSubscribed()
@@ -107,7 +107,8 @@ class SearchVM(
107107
}
108108
}
109109

110-
private companion object {
111-
const val QUERY_KEY = "com.hoc.flowmvi.ui.search.query"
110+
internal companion object {
111+
private const val QUERY_KEY = "com.hoc.flowmvi.ui.search.query"
112+
internal val SEARCH_DEBOUNCE_DURATION = Duration.milliseconds(400)
112113
}
113114
}

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

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.hoc.flowmvi.ui.search
2+
3+
import androidx.lifecycle.SavedStateHandle
4+
import arrow.core.right
5+
import com.flowmvi.mvi_testing.BaseMviViewModelTest
6+
import com.flowmvi.mvi_testing.mapRight
7+
import com.hoc.flowmvi.domain.usecase.SearchUsersUseCase
8+
import io.mockk.coEvery
9+
import io.mockk.coVerify
10+
import io.mockk.confirmVerified
11+
import io.mockk.mockk
12+
import kotlinx.coroutines.ExperimentalCoroutinesApi
13+
import kotlinx.coroutines.FlowPreview
14+
import kotlinx.coroutines.delay
15+
import kotlinx.coroutines.flow.flow
16+
import kotlin.test.Test
17+
import kotlin.time.Duration
18+
import kotlin.time.ExperimentalTime
19+
20+
@ExperimentalCoroutinesApi
21+
@FlowPreview
22+
@ExperimentalTime
23+
class SearchVMTest : BaseMviViewModelTest<ViewIntent, ViewState, SingleEvent, SearchVM>() {
24+
private lateinit var vm: SearchVM
25+
private lateinit var savedStateHandle: SavedStateHandle
26+
private lateinit var searchUsersUseCase: SearchUsersUseCase
27+
28+
override fun setup() {
29+
super.setup()
30+
31+
searchUsersUseCase = mockk() {
32+
coEvery { this@mockk(any()) } returns USERS.right()
33+
}
34+
35+
savedStateHandle = SavedStateHandle()
36+
vm = SearchVM(
37+
searchUsersUseCase = searchUsersUseCase,
38+
savedStateHandle = savedStateHandle
39+
)
40+
}
41+
42+
override fun tearDown() {
43+
confirmVerified(
44+
searchUsersUseCase,
45+
)
46+
super.tearDown()
47+
}
48+
49+
@Test
50+
fun test_withSearchIntent_rejectBlankSearchQuery() {
51+
val q = " "
52+
test(
53+
vmProducer = { vm },
54+
intents = flow {
55+
emit(ViewIntent.Search(q))
56+
timeout()
57+
},
58+
expectedStates = listOf(
59+
ViewState.initial(null),
60+
ViewState(
61+
users = emptyList(),
62+
isLoading = false,
63+
error = null,
64+
submittedQuery = "",
65+
originalQuery = q, // update originalQuery
66+
),
67+
).mapRight(),
68+
expectedEvents = emptyList(),
69+
delayAfterDispatchingIntents = TIMEOUT,
70+
)
71+
}
72+
73+
@Test
74+
fun test_withSearchIntent_returnsUserItemsWithProperLoadingState() {
75+
val q = "query"
76+
test(
77+
vmProducer = { vm },
78+
intents = flow {
79+
emit(ViewIntent.Search(q))
80+
timeout()
81+
},
82+
expectedStates = listOf(
83+
ViewState.initial(null),
84+
ViewState(
85+
users = emptyList(),
86+
isLoading = false,
87+
error = null,
88+
submittedQuery = "",
89+
originalQuery = q, // update originalQuery
90+
),
91+
ViewState(
92+
users = emptyList(),
93+
isLoading = true, // update isLoading
94+
error = null,
95+
submittedQuery = "",
96+
originalQuery = q,
97+
),
98+
ViewState(
99+
users = USER_ITEMS, // update users
100+
isLoading = false, // update isLoading
101+
error = null,
102+
submittedQuery = q,
103+
originalQuery = q,
104+
),
105+
).mapRight(),
106+
expectedEvents = emptyList(),
107+
delayAfterDispatchingIntents = TIMEOUT,
108+
) {
109+
coVerify(exactly = 1) { searchUsersUseCase(q) }
110+
}
111+
}
112+
113+
private companion object {
114+
private val TIMEOUT = Duration.milliseconds(100)
115+
116+
/**
117+
* Extra delay to emit search intent
118+
*/
119+
private suspend inline fun timeout() =
120+
delay(SearchVM.SEARCH_DEBOUNCE_DURATION + TIMEOUT)
121+
}
122+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.hoc.flowmvi.ui.search
2+
3+
import com.hoc.flowmvi.domain.model.User
4+
import com.hoc.flowmvi.test_utils.valueOrThrow
5+
6+
internal val USERS = listOf(
7+
User.create(
8+
id = "1",
9+
email = "[email protected]",
10+
firstName = "first1",
11+
lastName = "last1",
12+
avatar = "1.png"
13+
),
14+
User.create(
15+
id = "2",
16+
email = "[email protected]",
17+
firstName = "first2",
18+
lastName = "last2",
19+
avatar = "2.png"
20+
),
21+
User.create(
22+
id = "3",
23+
email = "[email protected]",
24+
firstName = "first3",
25+
lastName = "last3",
26+
avatar = "3.png"
27+
),
28+
).map { it.valueOrThrow }
29+
30+
internal val USER_ITEMS = USERS.map(UserItem::from)

0 commit comments

Comments
 (0)