Skip to content

Commit 9ae1c16

Browse files
committed
Unit Testing | Patch 25
1 parent 0b894a1 commit 9ae1c16

File tree

13 files changed

+901
-24
lines changed

13 files changed

+901
-24
lines changed

core/validation/src/test/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImplTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ class UrlValidatorImplTest {
5858
Assert.assertEquals(expected, actual)
5959
}
6060

61+
@Test
62+
fun `given input is url with port 99999, expected not valid with BadPort error`() {
63+
val expected = ValidationResult<UrlValidator.Error>(
64+
isValid = false,
65+
validationError = UrlValidator.Error.BadPort,
66+
)
67+
val actual = validator("http://5598.is.my.favorite.com:99999")
68+
Assert.assertEquals(expected, actual)
69+
}
70+
6171
@Test
6272
fun `given input is http localhost ipv4 address, expected not valid with Localhost error`() {
6373
val expected = ValidationResult<UrlValidator.Error>(
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
package com.shifthackz.aisdv1.domain.entity
22

33
data class Settings(
4-
val serverUrl: String,
5-
val sdModel: String,
6-
val demoMode: Boolean,
7-
val monitorConnectivity: Boolean,
8-
val autoSaveAiResults: Boolean,
9-
val saveToMediaStore: Boolean,
10-
val formAdvancedOptionsAlwaysShow: Boolean,
11-
val formPromptTaggedInput: Boolean,
12-
val source: ServerSource,
13-
val hordeApiKey: String,
14-
val localUseNNAPI: Boolean,
15-
val designUseSystemColorPalette: Boolean,
16-
val designUseSystemDarkTheme: Boolean,
17-
val designDarkTheme: Boolean,
18-
val designColorToken: String,
19-
val designDarkThemeToken: String,
4+
val serverUrl: String = "",
5+
val sdModel: String = "",
6+
val demoMode: Boolean = false,
7+
val monitorConnectivity: Boolean = false,
8+
val autoSaveAiResults: Boolean = false,
9+
val saveToMediaStore: Boolean = false,
10+
val formAdvancedOptionsAlwaysShow: Boolean = false,
11+
val formPromptTaggedInput: Boolean = false,
12+
val source: ServerSource = ServerSource.AUTOMATIC1111,
13+
val hordeApiKey: String = "",
14+
val localUseNNAPI: Boolean = false,
15+
val designUseSystemColorPalette: Boolean = false,
16+
val designUseSystemDarkTheme: Boolean = false,
17+
val designDarkTheme: Boolean = false,
18+
val designColorToken: String = "",
19+
val designDarkThemeToken: String = "",
2020
)

presentation/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ android {
1414
composeOptions {
1515
kotlinCompilerExtensionVersion = "1.5.7"
1616
}
17+
testOptions {
18+
unitTests.returnDefaultValues = true
19+
unitTests.all {
20+
jvmArgs(
21+
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
22+
"--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED"
23+
)
24+
}
25+
}
1726
}
1827

1928
dependencies {

presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/list/GalleryViewModel.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class GalleryViewModel(
6565
}
6666
}
6767

68-
6968
private fun launchGalleryExport() = galleryExporter()
7069
.doOnSubscribe { setActiveModal(Modal.ExportInProgress) }
7170
.subscribeOnMainThread(schedulersProvider)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.shifthackz.aisdv1.presentation.core
2+
3+
enum class CoreViewModelInitializeStrategy {
4+
InitializeOnce,
5+
InitializeEveryTime;
6+
}

presentation/src/test/java/com/shifthackz/aisdv1/presentation/core/CoreViewModelTest.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package com.shifthackz.aisdv1.presentation.core
44

55
import androidx.lifecycle.ViewModel
6+
import kotlinx.coroutines.CoroutineDispatcher
67
import kotlinx.coroutines.Dispatchers
78
import kotlinx.coroutines.ExperimentalCoroutinesApi
89
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -13,18 +14,31 @@ import org.junit.Before
1314

1415
abstract class CoreViewModelTest<V : ViewModel> {
1516

16-
private lateinit var _viewModel: V
17+
private var _viewModel: V? = null
1718

1819
protected val viewModel: V
19-
get() {
20-
if (this::_viewModel.isInitialized) return _viewModel
21-
_viewModel = initializeViewModel()
22-
return _viewModel
20+
get() = when (testViewModelStrategy) {
21+
CoreViewModelInitializeStrategy.InitializeOnce -> _viewModel ?: run {
22+
val vm = initializeViewModel()
23+
_viewModel = vm
24+
vm
25+
}
26+
CoreViewModelInitializeStrategy.InitializeEveryTime -> {
27+
val vm = initializeViewModel()
28+
_viewModel = vm
29+
vm
30+
}
2331
}
2432

33+
open val testViewModelStrategy: CoreViewModelInitializeStrategy
34+
get() = CoreViewModelInitializeStrategy.InitializeOnce
35+
36+
open val testDispatcher: CoroutineDispatcher
37+
get() = StandardTestDispatcher()
38+
2539
@Before
2640
open fun initialize() {
27-
Dispatchers.setMain(StandardTestDispatcher())
41+
Dispatchers.setMain(testDispatcher)
2842
}
2943

3044
@After
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.shifthackz.aisdv1.presentation.mocks
2+
3+
import com.shifthackz.aisdv1.domain.entity.StableDiffusionModel
4+
5+
val mockStableDiffusionModels = listOf(
6+
StableDiffusionModel(
7+
title = "title_5598",
8+
modelName = "name_5598",
9+
hash = "hash_5598",
10+
sha256 = "sha_5598",
11+
filename = "file_5598",
12+
config = "config_5598",
13+
) to true,
14+
StableDiffusionModel(
15+
title = "title_151297",
16+
modelName = "name_151297",
17+
hash = "hash_151297",
18+
sha256 = "sha_151297",
19+
filename = "file_151297",
20+
config = "config_151297",
21+
) to false,
22+
)

presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailViewModelTest.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ class GalleryDetailViewModelTest : CoreViewModelTest<GalleryDetailViewModel>() {
116116
}
117117

118118
@Test
119-
fun `given received Delete Confirm intent, expected modal field in UI state is None, deleteGalleryItemUseCase() method is called`() {
119+
fun `given received Delete Confirm intent, expected screenModal field in UI state is None, deleteGalleryItemUseCase() method is called`() {
120120
every {
121121
stubDeleteGalleryItemUseCase(any())
122122
} returns Completable.complete()
123+
123124
viewModel.processIntent(GalleryDetailIntent.Delete.Confirm)
125+
124126
runTest {
125127
val expected = Modal.None
126128
val actual = (viewModel.state.value as? GalleryDetailState.Content)?.screenModal
@@ -211,4 +213,28 @@ class GalleryDetailViewModelTest : CoreViewModelTest<GalleryDetailViewModel>() {
211213
)
212214
}
213215
}
216+
217+
@Test
218+
fun `given received SendTo Img2Img intent, expected router navigateBack() and form event update() methods called`() {
219+
viewModel.processIntent(GalleryDetailIntent.SendTo.Img2Img)
220+
verify {
221+
stubMainRouter.navigateBack()
222+
}
223+
verify {
224+
stubGenerationFormUpdateEvent.update(
225+
mockAiGenerationResult,
226+
AiGenerationResult.Type.IMAGE_TO_IMAGE,
227+
)
228+
}
229+
}
230+
231+
@Test
232+
fun `given received DismissDialog intent, expected screenModal field in UI state is None`() {
233+
viewModel.processIntent(GalleryDetailIntent.DismissDialog)
234+
runTest {
235+
val expected = Modal.None
236+
val actual = viewModel.state.value.screenModal
237+
Assert.assertEquals(expected, actual)
238+
}
239+
}
214240
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@file:OptIn(ExperimentalCoroutinesApi::class)
2+
3+
package com.shifthackz.aisdv1.presentation.screen.gallery.list
4+
5+
import android.graphics.Bitmap
6+
import android.net.Uri
7+
import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter
8+
import com.shifthackz.aisdv1.domain.entity.MediaStoreInfo
9+
import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCase
10+
import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase
11+
import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest
12+
import com.shifthackz.aisdv1.presentation.model.Modal
13+
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
14+
import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider
15+
import io.mockk.every
16+
import io.mockk.mockk
17+
import io.mockk.verify
18+
import io.reactivex.rxjava3.core.Single
19+
import kotlinx.coroutines.Dispatchers
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import kotlinx.coroutines.flow.firstOrNull
22+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
23+
import kotlinx.coroutines.test.runTest
24+
import kotlinx.coroutines.test.setMain
25+
import org.junit.Assert
26+
import org.junit.Before
27+
import org.junit.Test
28+
import java.io.File
29+
30+
class GalleryViewModelTest : CoreViewModelTest<GalleryViewModel>() {
31+
32+
private val stubMediaStoreInfo = MediaStoreInfo(5598)
33+
private val stubFile = mockk<File>()
34+
private val stubBitmap = mockk<Bitmap>()
35+
private val stubUri = mockk<Uri>()
36+
private val stubGetMediaStoreInfoUseCase = mockk<GetMediaStoreInfoUseCase>()
37+
private val stubGetGenerationResultPagedUseCase = mockk<GetGenerationResultPagedUseCase>()
38+
private val stubBase64ToBitmapConverter = mockk<Base64ToBitmapConverter>()
39+
private val stubGalleryExporter = mockk<GalleryExporter>()
40+
private val stubMainRouter = mockk<MainRouter>()
41+
42+
override fun initializeViewModel() = GalleryViewModel(
43+
getMediaStoreInfoUseCase = stubGetMediaStoreInfoUseCase,
44+
getGenerationResultPagedUseCase = stubGetGenerationResultPagedUseCase,
45+
base64ToBitmapConverter = stubBase64ToBitmapConverter,
46+
galleryExporter = stubGalleryExporter,
47+
schedulersProvider = stubSchedulersProvider,
48+
mainRouter = stubMainRouter,
49+
)
50+
51+
@Before
52+
override fun initialize() {
53+
super.initialize()
54+
55+
every {
56+
stubGetMediaStoreInfoUseCase()
57+
} returns Single.just(stubMediaStoreInfo)
58+
}
59+
60+
@Test
61+
fun `initialized, expected mediaStoreInfo field in UI state equals stubMediaStoreInfo`() {
62+
runTest {
63+
val expected = stubMediaStoreInfo
64+
val actual = viewModel.state.value.mediaStoreInfo
65+
Assert.assertEquals(expected, actual)
66+
}
67+
}
68+
69+
@Test
70+
fun `given received DismissDialog intent, expected screenModal field in UI state is None`() {
71+
viewModel.processIntent(GalleryIntent.DismissDialog)
72+
runTest {
73+
val expected = Modal.None
74+
val actual = viewModel.state.value.screenModal
75+
Assert.assertEquals(expected, actual)
76+
}
77+
}
78+
79+
@Test
80+
fun `given received Export Request intent, expected screenModal field in UI state is ConfirmExport`() {
81+
viewModel.processIntent(GalleryIntent.Export.Request)
82+
runTest {
83+
val expected = Modal.ConfirmExport
84+
val actual = viewModel.state.value.screenModal
85+
Assert.assertEquals(expected, actual)
86+
}
87+
}
88+
89+
@Test
90+
fun `given received Export Confirm intent, expected screenModal field in UI state is None, Share effect delivered to effect collector`() {
91+
every {
92+
stubGalleryExporter()
93+
} returns Single.just(stubFile)
94+
95+
Dispatchers.setMain(UnconfinedTestDispatcher())
96+
97+
viewModel.processIntent(GalleryIntent.Export.Confirm)
98+
99+
runTest {
100+
val expectedUiState = Modal.None
101+
val actualUiState = viewModel.state.value.screenModal
102+
Assert.assertEquals(expectedUiState, actualUiState)
103+
104+
val expectedEffect = GalleryEffect.Share(stubFile)
105+
val actualEffect = viewModel.effect.firstOrNull()
106+
Assert.assertEquals(expectedEffect, actualEffect)
107+
}
108+
verify {
109+
stubGalleryExporter()
110+
}
111+
}
112+
113+
@Test
114+
fun `given received OpenItem intent, expected router navigateToGalleryDetails() method called`() {
115+
every {
116+
stubMainRouter.navigateToGalleryDetails(any())
117+
} returns Unit
118+
119+
val item = GalleryGridItemUi(5598L, stubBitmap)
120+
viewModel.processIntent(GalleryIntent.OpenItem(item))
121+
122+
verify {
123+
stubMainRouter.navigateToGalleryDetails(5598L)
124+
}
125+
}
126+
127+
@Test
128+
fun `given received OpenMediaStoreFolder intent, expected OpenUri effect delivered to effect collector`() {
129+
Dispatchers.setMain(UnconfinedTestDispatcher())
130+
viewModel.processIntent(GalleryIntent.OpenMediaStoreFolder(stubUri))
131+
runTest {
132+
val expected = GalleryEffect.OpenUri(stubUri)
133+
val actual = viewModel.effect.firstOrNull()
134+
Assert.assertEquals(expected, actual)
135+
}
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.shifthackz.aisdv1.presentation.screen.home
2+
3+
import com.shifthackz.aisdv1.domain.entity.AiGenerationResult
4+
import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest
5+
import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent
6+
import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider
7+
import com.shifthackz.aisdv1.presentation.utils.Constants
8+
import io.mockk.every
9+
import io.mockk.mockk
10+
import io.reactivex.rxjava3.core.BackpressureStrategy
11+
import io.reactivex.rxjava3.subjects.BehaviorSubject
12+
import kotlinx.coroutines.flow.firstOrNull
13+
import kotlinx.coroutines.test.runTest
14+
import org.junit.Assert
15+
import org.junit.Before
16+
import org.junit.Test
17+
18+
class HomeNavigationViewModelTest : CoreViewModelTest<HomeNavigationViewModel>() {
19+
20+
private val stubRoute = BehaviorSubject.create<AiGenerationResult.Type>()
21+
private val stubGenerationFormUpdateEvent = mockk<GenerationFormUpdateEvent>()
22+
23+
override fun initializeViewModel() = HomeNavigationViewModel(
24+
generationFormUpdateEvent = stubGenerationFormUpdateEvent,
25+
schedulersProvider = stubSchedulersProvider,
26+
)
27+
28+
@Before
29+
override fun initialize() {
30+
super.initialize()
31+
32+
every {
33+
stubGenerationFormUpdateEvent.observeRoute()
34+
} returns stubRoute.toFlowable(BackpressureStrategy.LATEST)
35+
}
36+
37+
@Test
38+
fun `given generation form event is IMAGE_TO_IMAGE, expected HomeNavigationEffect with route ROUTE_IMG_TO_IMG delivered to effect collector`() {
39+
stubRoute.onNext(AiGenerationResult.Type.IMAGE_TO_IMAGE)
40+
runTest {
41+
val actual = HomeNavigationEffect(Constants.ROUTE_IMG_TO_IMG)
42+
val expected = viewModel.effect.firstOrNull()
43+
Assert.assertEquals(expected, actual)
44+
}
45+
}
46+
47+
@Test
48+
fun `given generation form event is TEXT_TO_IMAGE, expected HomeNavigationEffect with route ROUTE_TXT_TO_IMG delivered to effect collector`() {
49+
stubRoute.onNext(AiGenerationResult.Type.TEXT_TO_IMAGE)
50+
runTest {
51+
val actual = HomeNavigationEffect(Constants.ROUTE_TXT_TO_IMG)
52+
val expected = viewModel.effect.firstOrNull()
53+
Assert.assertEquals(expected, actual)
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)