test: Add Unit Test for :feature:auth module#1871
test: Add Unit Test for :feature:auth module#1871niyajali merged 11 commits intoopenMF:developmentfrom
:feature:auth module#1871Conversation
| state.savingsProductId == 0 -> { | ||
| mutableStateFlow.update { | ||
| it.copy(dialogState = SignUpDialog.Error("Please select a savings account.")) | ||
| it.copy(dialogState = SignUpDialog.Error(SELECT_SAVINGS_ACCOUNT)) |
There was a problem hiding this comment.
Revert it back to previous as we don't use Constants for these messages we are gonna use stringResource which if we target different audience we will use localization
feature/auth/build.gradle.kts
Outdated
| commonTest.dependencies { | ||
| implementation(kotlin("test-common")) | ||
| implementation(kotlin("test-annotations-common")) | ||
| implementation(libs.kotlinx.coroutines.test) |
There was a problem hiding this comment.
- Add the dependencies if it is needs for example
implementation(libs.kotlinx.coroutines.test)this one is already added inKMPLibraryConventionPlugin.ktorimplementation(compose.uiTest)as we are not focusing in UI test don't add it and other dependecnies that you added. - @niyajali should he use
mokerrythird party library for testing?
| } | ||
|
|
||
| @Test | ||
| fun `username change updates state`() = runTest(testDispatcher) { |
There was a problem hiding this comment.
use one style for naming the test.
for example loginViewModel_UsernameChanged_UpdatesState
| viewModel.trySendAction(LoginAction.PasswordChanged("testpass")) | ||
| viewModel.trySendAction(LoginAction.LoginClicked) | ||
|
|
||
| println(viewModel.stateFlow.value) |
| const val UNAUTHORIZED_ERROR = "401 Unauthorized" | ||
|
|
||
| const val SELECT_SAVINGS_ACCOUNT = "Please select a savings account." | ||
| const val ENTER_FIRST_NAME = "Please enter your first name." |
There was a problem hiding this comment.
remove these added these constants as i mentioned the reason below
|
@niyajali if you have any suggestions for him regarding to which format or how to write unit testing regarding our format let him know |
feature/auth/build.gradle.kts
Outdated
| alias(libs.plugins.mifospay.cmp.feature) | ||
| alias(libs.plugins.kotlin.parcelize) | ||
| alias(libs.plugins.kotlin.serialization) | ||
| id("dev.mokkery") version "2.7.2" |
There was a problem hiding this comment.
This direct version dependency should be replaced. Please use alias(libs.mokkery) instead to keep it consistent with the version catalog.
| state.mobileNumberInput.isEmpty() -> { | ||
| mutableStateFlow.update { | ||
| it.copy(dialogState = SignUpDialog.Error("Please enter your mobile number.")) | ||
| it.copy(dialogState = SignUpDialog.Error("Please enter a your mobile number.")) |
There was a problem hiding this comment.
adding a is a grammatical mistake this should not ne added
| "\n- At least one lowercase character" + | ||
| "\n- At least one numeric digit" + | ||
| "\n- At least one special character" | ||
| ?: """ |
There was a problem hiding this comment.
don't remove the \n otherwise it will show all message attach to each other
revert any changes regarding the text
| state.countryInput.isEmpty() -> { | ||
| mutableStateFlow.update { | ||
| it.copy(dialogState = SignUpDialog.Error("Please enter your country.")) | ||
| it.copy(dialogState = SignUpDialog.Error("Please enter your country")) |
There was a problem hiding this comment.
just one thing keep the text as the way were before
|
@Shreyash16b fix the things that i mentioned and give me the authority to push changes to this PR. once i get the change i will pull it |
|
@Shreyash16b don't push any new code for now. just give me the authority so i do it |
|
@HekmatullahAmin Sorry, I just pushed the code a minute before your message. |
DescriptionThis PR improves test coverage and developer experience in the authentication feature by:
AndroidSignUpViewModelTest
LoginViewModelTest
MobileVerificationViewModelTest
IosDesktopWasmJsBrowserJsNode |
|
|
||
| import org.mifospay.core.model.search.SearchResult | ||
|
|
||
| val fakeSearchResult = SearchResult( |
There was a problem hiding this comment.
Move this directly on that file where it has been used no need to create separate file for this
| Enhancement: Move the following code in to a Use Case | ||
| */ | ||
| private fun initiateSignUp() { | ||
| DialogManager.showLoading() |
There was a problem hiding this comment.
When we introduced DialogManager, @HekmatullahAmin are you sure that these changes are necessary
There was a problem hiding this comment.
@niyajali Previously i created so to have one object handle the dialog logic in all our screens and cause of Parcelization we couldn't persist those of StringResource type. Although we can achieve the unit testing with DialogManager, but it will bound to lifecycle of viewmodel and also in unit testing then we also need to call it to check it is state.
|
|
||
| import org.mifospay.core.model.user.UserInfo | ||
|
|
||
| val fakeUserInfo = UserInfo( |
There was a problem hiding this comment.
this too and make it internal
| Dispatchers.resetMain() | ||
| } | ||
|
|
||
| // -------------------------------------------------------------------------- |
| * Verifies initial state values are set correctly on ViewModel init. | ||
| */ | ||
| @Test | ||
| fun loginViewModel_InitialState_ValidInitialConditions() = runTest(testDispatcher) { |
There was a problem hiding this comment.
remove the prefix from all test methods, and try to followT, est method should follow the given_when_then or when_then format.
| assertFalse(viewModel.stateFlow.value.isPasswordVisible) | ||
|
|
||
| viewModel.trySendAction(LoginAction.TogglePasswordVisibility) | ||
| advanceUntilIdle() |
There was a problem hiding this comment.
we won't need this always, remove the testDispatcher from this lamda runTest(testDispatcher) as you've defined in setup method, use turbine libray to test the flow
There was a problem hiding this comment.
There was a problem hiding this comment.
And this is not a KMP project, but you can check it out just for reference - https://github.com/devikontech/restaurant-billing-pos/tree/main/feature/addonitem/src/test/kotlin/com/niyaj/addonitem
There was a problem hiding this comment.
i removed it, but we don't have any flow in the view models to test
There was a problem hiding this comment.
viewModel.stateFlow, viewModel.eventFlow are flowable property
There was a problem hiding this comment.
I understand your point, but testing can be inconsistent and really sucks sometimes in my experience, sometimes the UI displays correct output while tests fail, or tests pass locally in your IDE but fail in CI.
This is why I'd suggest combining both approaches. When collecting values with .value, you might need to use waitUntilIdle() or collect the flow properly, since flows only emit values when there's an active collector. However, this might not be necessary in some cases as this project designed UDF (Unidirectional Data Flow) pattern.
Let's implement what works best for our use case, and for reference, check the Now in Android project or other similar examples given above.
There was a problem hiding this comment.
Our BaseViewModel handles every user action asynchronously via viewModelScope.launch, using advanceUntilIdle() is necessary to ensure state changes are fully applied before we assert on stateFlow.value.
viewModelScope.launch {
internalActionChannel.consumeAsFlow().collect { action -> handleAction(action) }
}
This means:
Even if handleAction updates the state immediately, it's still queued on a coroutine and will run in a future dispatch cycle.
So when your test does:
viewModel.trySendAction(...)
assertEquals(expected, viewModel.stateFlow.value)
...it might assert too early, before handleAction actually runs.
That's why you use advanceUntilIdle() — it gives the coroutine scheduler time to process any enqueued tasks before you assert.
-
.value makes perfect sense when we only care about the final, conflated state.
-
That said, Turbine is a great fit when we need to capture multiple state emissions over time.
So going forward, I’ll continue using .value + advanceUntilIdle() for most simple stateFlow assertions, but I’ll switch to using Turbine for eventFlow testing to ensure we properly collect and verify emitted events.
below is an example for both approach which i think using turbine here is not necessary our logic is more simple in first one:
@Test
fun givenFirstName_whenInputChanged_thenFirstNameIsUpdated() = runTest {
val firstName = "John"
viewModel.trySendAction(SignUpAction.FirstNameInputChange(firstName))
advanceUntilIdle()
assertEquals(firstName, viewModel.stateFlow.value.firstNameInput)
}
@Test
fun givenFirstNames_whenInputChanged_thenFirstNameIsUpdated() = runTest {
viewModel.stateFlow.test {
// Skip initial state
skipItems(1)
// Act: send username change
val firstName = "John"
viewModel.trySendAction(SignUpAction.FirstNameInputChange(firstName))
// Assert: username should now be "alice"
val state = awaitItem()
assertEquals(firstName, state.firstNameInput)
}
}
There was a problem hiding this comment.
Yes, I meant the same, refactor all the test cases accordingly and use proper naming conventions like given_when_then or when_then and let me know.
There was a problem hiding this comment.
@niyajali yeah already used given_when_then naming convention.
and also added unit test for eventFlow
There was a problem hiding this comment.
I’ve directly tested the stateFlow using .value along with advanceUntilIdle(), as done in the Now in Android project. Since our logic is relatively simple and we only care about the latest state, using Turbine would’ve added unnecessary checks like skipItems() or awaitItem() that don’t add value in this case.
| import kotlin.test.assertTrue | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| class MobileVerificationViewModelTest { |
There was a problem hiding this comment.
Similarly apply those changes on these test classes
:feature:auth module
|
@HekmatullahAmin Upload Test results for all platforms |
@niyajali done. i run one viewmodel test in one platform if i did all it would take a lot of space. and also could u just cut it and paste it in PR description i don't have authority to do it. |







Issue Fix
Fixes 221
Jira Task: 221
Screenshots
Description
This is the error while running command

./gradlew :feature:auth:jsTestThis is the error while running command

./gradlew :feature:auth:wasmJsBrowserTestApply the
AndroidStyle.xmlstyle template to your code in Android Studio.[✅] Run the unit tests with
./gradlew checkto make sure you didn't break anything[✅] If you have multiple commits please combine them into one commit by squashing them.