Skip to content

Commit 3ac4077

Browse files
committed
Unit Testing | Patch 21
1 parent fef4c21 commit 3ac4077

File tree

5 files changed

+214
-4
lines changed

5 files changed

+214
-4
lines changed

dependencies.gradle

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ext {
2626
googleMaterialVersion = '1.9.0'
2727
accompanistSystemUiControllerVersion = '0.30.1'
2828
cryptoVersion = '1.0.0'
29+
exifinterfaceVersion = '1.3.6'
2930
onnxruntimeVersion = '1.16.3'
3031
catppuccinVersion = '0.1.1'
3132
composeGesturesVersion = '3.1'
@@ -34,6 +35,8 @@ ext {
3435
testJunitVersion = '4.13.2'
3536
testMockitoVersion = '2.2.0'
3637
testMockkVersion = '1.13.11'
38+
testCoroutinesVersion = '1.8.1'
39+
testTurbibeVersion = '1.1.0'
3740

3841
androidx = [
3942
core : "androidx.core:core-ktx:$coreKtxVersion",
@@ -55,6 +58,7 @@ ext {
5558
pagingRx3 : "androidx.paging:paging-rxjava3:$pagingVersion",
5659
pagingCompose : "androidx.paging:paging-compose:$pagingComposeVersion",
5760
crypto : "androidx.security:security-crypto:$cryptoVersion",
61+
exifinterface : "androidx.exifinterface:exifinterface:$exifinterfaceVersion",
5862
]
5963
google = [
6064
gson : "com.google.code.gson:gson:$gsonVersion",
@@ -106,8 +110,10 @@ ext {
106110
stringutils: "org.apache.commons:commons-lang3:$apacheLangVersion"
107111
]
108112
test = [
109-
junit : "junit:junit:$testJunitVersion",
110-
mockito: "com.nhaarman.mockitokotlin2:mockito-kotlin:$testMockitoVersion",
111-
mockk : "io.mockk:mockk:$testMockkVersion",
113+
junit : "junit:junit:$testJunitVersion",
114+
mockito : "com.nhaarman.mockitokotlin2:mockito-kotlin:$testMockitoVersion",
115+
mockk : "io.mockk:mockk:$testMockkVersion",
116+
coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-test:$testCoroutinesVersion",
117+
turbine : "app.cash.turbine:turbine:$testTurbibeVersion",
112118
]
113119
}

presentation/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
implementation androidx.appcompat
3232
implementation androidx.activity
3333
implementation androidx.pagingRx3
34+
implementation androidx.exifinterface
3435

3536
implementation google.material
3637
implementation apache.stringutils
@@ -45,5 +46,9 @@ dependencies {
4546
implementation ui.catppuccinSplashscreen
4647
implementation ui.composeGestures
4748
implementation ui.composeEasyCrop
48-
implementation "androidx.exifinterface:exifinterface:1.3.6"
49+
50+
testImplementation test.junit
51+
testImplementation test.mockk
52+
testImplementation test.coroutines
53+
testImplementation test.turbine
4954
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.shifthackz.aisdv1.presentation.activity
2+
3+
import androidx.navigation.NavOptionsBuilder
4+
import app.cash.turbine.test
5+
import com.shifthackz.aisdv1.domain.preference.PreferenceManager
6+
import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest
7+
import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect
8+
import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter
9+
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
10+
import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider
11+
import io.mockk.every
12+
import io.mockk.mockk
13+
import io.mockk.verify
14+
import io.reactivex.rxjava3.subjects.BehaviorSubject
15+
import kotlinx.coroutines.flow.firstOrNull
16+
import kotlinx.coroutines.test.runTest
17+
import org.junit.Assert
18+
import org.junit.Before
19+
import org.junit.Test
20+
21+
class AiStableDiffusionViewModelTest : CoreViewModelTest<AiStableDiffusionViewModel>() {
22+
23+
private val stubNavigationEffect = BehaviorSubject.create<NavigationEffect>()
24+
private val stubDrawerNavigationEffect = BehaviorSubject.create<NavigationEffect.Drawer>()
25+
private val stubNavBuilder: NavOptionsBuilder.() -> Unit = {
26+
popUpTo("splash") { inclusive = true }
27+
}
28+
29+
private val stubMainRouter = mockk<MainRouter>()
30+
private val stubDrawerRouter = mockk<DrawerRouter>()
31+
private val stubPreferenceManager = mockk<PreferenceManager>()
32+
33+
override fun initializeViewModel() = AiStableDiffusionViewModel(
34+
schedulersProvider = stubSchedulersProvider,
35+
mainRouter = stubMainRouter,
36+
drawerRouter = stubDrawerRouter,
37+
preferenceManager = stubPreferenceManager,
38+
)
39+
40+
@Before
41+
override fun initialize() {
42+
super.initialize()
43+
44+
every {
45+
stubMainRouter.observe()
46+
} returns stubNavigationEffect
47+
48+
every {
49+
stubDrawerRouter.observe()
50+
} returns stubDrawerNavigationEffect
51+
}
52+
53+
@Test
54+
fun `given onStoragePermissionsGranted was called, expected VM sets field saveToMediaStore with true in preference manager`() {
55+
every {
56+
stubPreferenceManager::saveToMediaStore.set(any())
57+
} returns Unit
58+
59+
viewModel.onStoragePermissionsGranted()
60+
61+
verify {
62+
stubPreferenceManager::saveToMediaStore.set(true)
63+
}
64+
}
65+
66+
@Test
67+
fun `given route event from main router, expected domain model delivered to effect collector`() {
68+
stubNavigationEffect.onNext(NavigationEffect.Navigate.Route("route"))
69+
runTest {
70+
val expected = NavigationEffect.Navigate.Route("route")
71+
val actual = viewModel.effect.firstOrNull()
72+
Assert.assertEquals(expected, actual)
73+
}
74+
}
75+
76+
@Test
77+
fun `given route pop up event from main router, expected domain model delivered to effect collector`() {
78+
stubNavigationEffect.onNext(NavigationEffect.Navigate.RoutePopUp("route"))
79+
runTest {
80+
val expected = NavigationEffect.Navigate.RoutePopUp("route")
81+
val actual = viewModel.effect.firstOrNull()
82+
Assert.assertEquals(expected, actual)
83+
}
84+
}
85+
86+
@Test
87+
fun `given route builder event from main router, expected domain model delivered to effect collector`() {
88+
stubNavigationEffect.onNext(
89+
NavigationEffect.Navigate.RouteBuilder("route", stubNavBuilder)
90+
)
91+
runTest {
92+
val expected = NavigationEffect.Navigate.RouteBuilder("route", stubNavBuilder)
93+
val actual = viewModel.effect.firstOrNull()
94+
Assert.assertEquals(expected, actual)
95+
}
96+
}
97+
98+
@Test
99+
fun `given back event from main router, expected domain model delivered to effect collector`() {
100+
stubNavigationEffect.onNext(NavigationEffect.Back)
101+
runTest {
102+
val expected = NavigationEffect.Back
103+
val actual = viewModel.effect.firstOrNull()
104+
Assert.assertEquals(expected, actual)
105+
}
106+
}
107+
108+
@Test
109+
fun `given route then back events from main router, expected two domain models delivered to effect collector in same order`() {
110+
runTest {
111+
viewModel.effect.test {
112+
stubNavigationEffect.onNext(NavigationEffect.Navigate.Route("route2"))
113+
Assert.assertEquals(NavigationEffect.Navigate.Route("route2"), awaitItem())
114+
115+
stubNavigationEffect.onNext(NavigationEffect.Back)
116+
Assert.assertEquals(NavigationEffect.Back, awaitItem())
117+
118+
cancelAndIgnoreRemainingEvents()
119+
}
120+
}
121+
}
122+
123+
@Test
124+
fun `given mixed six events from main router, expected six domain models delivered to effect collector in same order`() {
125+
runTest {
126+
viewModel.effect.test {
127+
stubNavigationEffect.onNext(NavigationEffect.Navigate.Route("route2"))
128+
Assert.assertEquals(NavigationEffect.Navigate.Route("route2"), awaitItem())
129+
130+
stubNavigationEffect.onNext(NavigationEffect.Navigate.Route("route4"))
131+
Assert.assertEquals(NavigationEffect.Navigate.Route("route4"), awaitItem())
132+
133+
stubNavigationEffect.onNext(NavigationEffect.Back)
134+
Assert.assertEquals(NavigationEffect.Back, awaitItem())
135+
136+
stubNavigationEffect.onNext(NavigationEffect.Navigate.Route("route3"))
137+
Assert.assertEquals(NavigationEffect.Navigate.Route("route3"), awaitItem())
138+
139+
stubNavigationEffect.onNext(NavigationEffect.Back)
140+
Assert.assertEquals(NavigationEffect.Back, awaitItem())
141+
142+
stubNavigationEffect.onNext(NavigationEffect.Back)
143+
Assert.assertEquals(NavigationEffect.Back, awaitItem())
144+
145+
cancelAndIgnoreRemainingEvents()
146+
}
147+
}
148+
}
149+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@file:OptIn(ExperimentalCoroutinesApi::class)
2+
3+
package com.shifthackz.aisdv1.presentation.core
4+
5+
import androidx.lifecycle.ViewModel
6+
import kotlinx.coroutines.Dispatchers
7+
import kotlinx.coroutines.ExperimentalCoroutinesApi
8+
import kotlinx.coroutines.test.StandardTestDispatcher
9+
import kotlinx.coroutines.test.resetMain
10+
import kotlinx.coroutines.test.setMain
11+
import org.junit.After
12+
import org.junit.Before
13+
14+
abstract class CoreViewModelTest<V : ViewModel> {
15+
16+
private lateinit var _viewModel: V
17+
18+
protected val viewModel: V
19+
get() {
20+
if (this::_viewModel.isInitialized) return _viewModel
21+
_viewModel = initializeViewModel()
22+
return _viewModel
23+
}
24+
25+
@Before
26+
open fun initialize() {
27+
Dispatchers.setMain(StandardTestDispatcher())
28+
}
29+
30+
@After
31+
open fun finalize() {
32+
Dispatchers.resetMain()
33+
}
34+
35+
abstract fun initializeViewModel(): V
36+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.shifthackz.aisdv1.presentation.stub
2+
3+
import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider
4+
import io.reactivex.rxjava3.core.Scheduler
5+
import io.reactivex.rxjava3.schedulers.Schedulers
6+
import java.util.concurrent.Executor
7+
import java.util.concurrent.Executors
8+
9+
val stubSchedulersProvider = object : SchedulersProvider {
10+
override val io: Scheduler = Schedulers.trampoline()
11+
override val ui: Scheduler = Schedulers.trampoline()
12+
override val computation: Scheduler = Schedulers.trampoline()
13+
override val singleThread: Executor = Executors.newSingleThreadExecutor()
14+
}

0 commit comments

Comments
 (0)