From 8bc287b0db09a60da23d486a2b5facd0315a11ed Mon Sep 17 00:00:00 2001 From: Mohit Rajput Date: Mon, 16 Jun 2025 04:21:19 -0700 Subject: [PATCH] NTP-65855 | bbps unit test base class setup and added example usage (#16392) Co-authored-by: Kishan Kumar --- .../bbps/feature/paybill/PayBillViewModel.kt | 4 +- .../com/navi/bbps/base/NaviBbpsBaseTest.kt | 70 +++ .../OriginBillDetectionHandlerTest.kt | 59 +- .../feature/paybill/PayBillViewModelTest.kt | 526 ++++++++++++++++++ 4 files changed, 605 insertions(+), 54 deletions(-) create mode 100644 android/navi-bbps/src/test/kotlin/com/navi/bbps/base/NaviBbpsBaseTest.kt create mode 100644 android/navi-bbps/src/test/kotlin/com/navi/bbps/feature/paybill/PayBillViewModelTest.kt diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/paybill/PayBillViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/paybill/PayBillViewModel.kt index ccf9e82020..22265a72ad 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/paybill/PayBillViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/paybill/PayBillViewModel.kt @@ -339,7 +339,7 @@ constructor( } } - private fun updateConsentProvided(isConsentProvided: Boolean) { + fun updateConsentProvided(isConsentProvided: Boolean) { _isConsentProvided.update { isConsentProvided } } @@ -363,7 +363,7 @@ constructor( _payBillScreenState.update { payBillScreenState } } - private fun updatePayBillHeaderState(payBillHeaderState: PayBillHeaderState) { + fun updatePayBillHeaderState(payBillHeaderState: PayBillHeaderState) { _payBillHeaderState.update { payBillHeaderState } } diff --git a/android/navi-bbps/src/test/kotlin/com/navi/bbps/base/NaviBbpsBaseTest.kt b/android/navi-bbps/src/test/kotlin/com/navi/bbps/base/NaviBbpsBaseTest.kt new file mode 100644 index 0000000000..29cd733f0c --- /dev/null +++ b/android/navi-bbps/src/test/kotlin/com/navi/bbps/base/NaviBbpsBaseTest.kt @@ -0,0 +1,70 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.base + +import com.navi.common.di.CoroutineDispatcherProvider +import io.mockk.MockKAnnotations +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before + +abstract class NaviBbpsBaseTest { + // Coroutine testing utilities + private val testScheduler = TestCoroutineScheduler() + val testDispatcher = StandardTestDispatcher(scheduler = testScheduler) + val testScope = TestScope(testDispatcher) + + /** + * Test implementation of CoroutineDispatcherProvider that uses a single test dispatcher for all + * coroutine contexts to make testing deterministic. + */ + class TestCoroutineDispatcherProvider(private val testDispatcher: TestDispatcher) : + CoroutineDispatcherProvider { + override val default: CoroutineDispatcher + get() = testDispatcher + + override val main: CoroutineDispatcher + get() = testDispatcher + + override val io: CoroutineDispatcher + get() = testDispatcher + } + + /** + * Set up the test environment before each test. + * 1. Configure Dispatchers.Main to use our test dispatcher + * 2. Initialize mock annotations + */ + @OptIn(ExperimentalCoroutinesApi::class) + @Before + open fun setUp() { + // Configure the main dispatcher for testing + Dispatchers.setMain(testDispatcher) + + // Initialize MockK annotations with relaxed behavior + MockKAnnotations.init(this, relaxed = true) + } + + /** + * Clean up after each test to avoid affecting other tests. Reset the main dispatcher to its + * original state. + */ + @OptIn(ExperimentalCoroutinesApi::class) + @After + open fun tearDown() { + Dispatchers.resetMain() + } +} diff --git a/android/navi-bbps/src/test/kotlin/com/navi/bbps/common/viewmodel/OriginBillDetectionHandlerTest.kt b/android/navi-bbps/src/test/kotlin/com/navi/bbps/common/viewmodel/OriginBillDetectionHandlerTest.kt index 252c9b7b8b..5848c325f5 100644 --- a/android/navi-bbps/src/test/kotlin/com/navi/bbps/common/viewmodel/OriginBillDetectionHandlerTest.kt +++ b/android/navi-bbps/src/test/kotlin/com/navi/bbps/common/viewmodel/OriginBillDetectionHandlerTest.kt @@ -9,6 +9,7 @@ package com.navi.bbps.common.viewmodel import com.navi.base.utils.NaviNetworkConnectivity import com.navi.base.utils.ResourceProvider +import com.navi.bbps.base.NaviBbpsBaseTest import com.navi.bbps.common.gmail.signin.GmailAccessSignInManager.Companion.GMAIL_READ_ONLY_SCOPE import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig import com.navi.bbps.common.model.network.BillDetectionResponse @@ -17,23 +18,12 @@ import com.navi.bbps.common.model.view.DetectedBillEntity import com.navi.bbps.common.model.view.FullScreenLoaderState import com.navi.bbps.feature.detectedbills.DetectedBillsRepository import com.navi.bbps.feature.mybills.MyBillsSyncJob -import com.navi.common.di.CoroutineDispatcherProvider import com.navi.common.network.models.GenericErrorResponse import com.navi.common.network.models.RepoResult -import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK import io.mockk.mockk -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -49,8 +39,7 @@ import org.junit.Test * - The startDetectingBills method with different scenarios * - Various helper functions within the handler */ -@OptIn(ExperimentalCoroutinesApi::class) -class OriginBillDetectionHandlerTest { +class OriginBillDetectionHandlerTest : NaviBbpsBaseTest() { // Dependencies to be mocked @MockK private lateinit var detectedBillsRepository: DetectedBillsRepository @MockK private lateinit var resourceProvider: ResourceProvider @@ -60,46 +49,12 @@ class OriginBillDetectionHandlerTest { // Class under test private lateinit var handler: OriginBillDetectionHandler - // Coroutine testing utilities - private val testScheduler = TestCoroutineScheduler() - private val testDispatcher = StandardTestDispatcher(scheduler = testScheduler) - private val testScope = TestScope(testDispatcher) - - /** - * Test implementation of CoroutineDispatcherProvider that uses a single test dispatcher for all - * coroutine contexts to make testing deterministic. - */ - class TestCoroutineDispatcherProviderImpl(private val testDispatcher: TestDispatcher) : - CoroutineDispatcherProvider { - override val default: CoroutineDispatcher - get() = testDispatcher - - override val main: CoroutineDispatcher - get() = testDispatcher - - override val io: CoroutineDispatcher - get() = testDispatcher - } - - /** - * Set up the test environment before each test. - * 1. Configure Dispatchers.Main to use our test dispatcher - * 2. Initialize mock annotations - * 3. Create the handler instance with test dependencies - */ - @OptIn(ExperimentalCoroutinesApi::class) @Before - fun setUp() { - // Configure the main dispatcher for testing - Dispatchers.setMain(testDispatcher) - - // Initialize MockK annotations with relaxed behavior - MockKAnnotations.init(this, relaxed = true) - - // Initialize the handler with test dependencies + override fun setUp() { + super.setUp() handler = OriginBillDetectionHandler( - coroutineDispatcherProvider = TestCoroutineDispatcherProviderImpl(testDispatcher), + coroutineDispatcherProvider = TestCoroutineDispatcherProvider(testDispatcher), detectedBillsRepository = detectedBillsRepository, resourceProvider = resourceProvider, naviNetworkConnectivity = naviNetworkConnectivity, @@ -367,8 +322,8 @@ class OriginBillDetectionHandlerTest { * original state. */ @After - fun tearDown() { - Dispatchers.resetMain() + override fun tearDown() { + super.tearDown() } /** diff --git a/android/navi-bbps/src/test/kotlin/com/navi/bbps/feature/paybill/PayBillViewModelTest.kt b/android/navi-bbps/src/test/kotlin/com/navi/bbps/feature/paybill/PayBillViewModelTest.kt new file mode 100644 index 0000000000..8340ca31ca --- /dev/null +++ b/android/navi-bbps/src/test/kotlin/com/navi/bbps/feature/paybill/PayBillViewModelTest.kt @@ -0,0 +1,526 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.feature.paybill + +import androidx.lifecycle.SavedStateHandle +import com.navi.base.utils.NaviNetworkConnectivity +import com.navi.base.utils.ResourceProvider +import com.navi.bbps.base.NaviBbpsBaseTest +import com.navi.bbps.common.CoinsSyncManager +import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig +import com.navi.bbps.common.repository.BbpsCommonRepository +import com.navi.bbps.common.session.NaviBbpsSessionHelper +import com.navi.bbps.common.session.ZeroPlatformFeeBottomSheetViewTracker +import com.navi.bbps.common.usecase.FetchBillHandler +import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase +import com.navi.bbps.common.usecase.RewardNudgeUseCase +import com.navi.bbps.common.utils.NaviBbpsDateUtils +import com.navi.bbps.common.utils.getDefaultConfig +import com.navi.bbps.feature.category.BillCategoriesRepository +import com.navi.bbps.feature.category.model.view.BillCategoryEntity +import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity +import com.navi.bbps.feature.customerinput.model.view.BillDetailsEntity +import com.navi.bbps.feature.customerinput.model.view.BillerAdditionalParamsEntity +import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity +import com.navi.bbps.feature.mybills.MyBillsRepository +import com.navi.bbps.feature.mybills.model.view.MyBillEntity +import com.navi.bbps.feature.paybill.helper.BbpsArcHelper +import com.navi.bbps.feature.paybill.helper.NotifyDuplicatePaymentHandler +import com.navi.bbps.feature.paybill.helper.PayBillAmountChipsHelper +import com.navi.bbps.feature.paybill.model.view.PayBillHeaderState +import com.navi.bbps.feature.paybill.model.view.PayBillSource +import com.navi.common.usecase.LitmusExperimentsUseCase +import com.navi.payment.nativepayment.utils.NaviPaymentRewardsEventBus +import com.navi.payment.tstore.repository.TStoreOrderHandler +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.invoke +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +/** + * Unit tests for PayBillViewModel. + * + * Tests cover important functionality including: + * - Credit card payment option handling + * - Bill fetch behavior via BillFetchHandler + * - Payment processing + * - Validation logic + */ +class PayBillViewModelTest : NaviBbpsBaseTest() { + @MockK private lateinit var savedStateHandle: SavedStateHandle + @MockK private lateinit var payBillRepository: PayBillRepository + @MockK private lateinit var naviBbpsDateUtils: NaviBbpsDateUtils + @MockK private lateinit var resourceProvider: ResourceProvider + @MockK private lateinit var billCategoriesRepository: BillCategoriesRepository + @MockK private lateinit var myBillsRepository: MyBillsRepository + @MockK private lateinit var naviBbpsCommonRepository: BbpsCommonRepository + @MockK private lateinit var naviNetworkConnectivity: NaviNetworkConnectivity + @MockK private lateinit var naviBbpsSessionHelper: NaviBbpsSessionHelper + @MockK private lateinit var fetchBillHandler: FetchBillHandler + @MockK private lateinit var coinsSyncManager: CoinsSyncManager + @MockK + private lateinit var zeroPlatformFeeBottomSheetViewTracker: + ZeroPlatformFeeBottomSheetViewTracker + @MockK private lateinit var litmusExperimentsUseCase: LitmusExperimentsUseCase + @MockK private lateinit var naviBbpsConfigUseCase: NaviBbpsConfigUseCase + @MockK private lateinit var rewardsNudgeEntityFetchUseCase: RewardNudgeUseCase + @MockK private lateinit var tStoreOrderHandler: TStoreOrderHandler + @MockK private lateinit var resProvider: ResourceProvider + @MockK private lateinit var naviPaymentRewardEventBus: NaviPaymentRewardsEventBus + @MockK private lateinit var payBillAmountChipsHelper: PayBillAmountChipsHelper + @MockK private lateinit var bbpsArcHelper: BbpsArcHelper + @MockK private lateinit var notifyDuplicatePaymentHandler: NotifyDuplicatePaymentHandler + + private lateinit var viewModel: PayBillViewModel + + @Before + override fun setUp() { + super.setUp() + + every { naviBbpsSessionHelper.getNaviBbpsSessionAttributes() } returns HashMap() + every { naviNetworkConnectivity.isInternetConnected() } returns true + + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + val mockBillerDetailsEntity = mockk(relaxed = true) + val payBillSource = + PayBillSource.Others( + billerDetailsEntity = mockBillerDetailsEntity, + billDetailsEntity = null, + customerParams = mapOf(), + formattedLastPaidDate = "", + formattedLastPaidAmount = "", + billerId = "test_biller_id", + amount = "100.00", + isConsentRequired = false, + ) + every { savedStateHandle.get("payBillScreenSource") } returns payBillSource + + val billCategoryEntity = + BillCategoryEntity( + categoryId = "test_category_id", + title = "Test Category", + iconUrl = "https://example.com/icon.png", + searchBoxPlaceholderText = "Search billers", + billerListStateHeading = "Popular billers", + billerListAllHeading = "All billers", + reminderTitle = "Set reminder", + ) + every { savedStateHandle.get("billCategoryEntity") } returns + billCategoryEntity + + val phoneContactEntity = mockk(relaxed = true) + every { savedStateHandle.get("phoneNumberDetail") } returns + phoneContactEntity + + every { savedStateHandle.get("isRootScreen") } returns true + every { savedStateHandle.get("isBillSyncRequiredInBackground") } returns false + every { savedStateHandle.get("source") } returns "test_source" + every { savedStateHandle.get("initialSource") } returns "test_initial_source" + + val naviBbpsDefaultConfig = mockk(relaxed = true) + coEvery { naviBbpsConfigUseCase.getDefaultConfig(screenName = "Pay bill") } returns + naviBbpsDefaultConfig + + viewModel = + PayBillViewModel( + savedStateHandle = savedStateHandle, + dispatcherProvider = TestCoroutineDispatcherProvider(testDispatcher), + payBillRepository = payBillRepository, + naviBbpsDateUtils = naviBbpsDateUtils, + resourceProvider = resourceProvider, + billCategoriesRepository = billCategoriesRepository, + myBillsRepository = myBillsRepository, + naviBbpsCommonRepository = naviBbpsCommonRepository, + naviNetworkConnectivity = naviNetworkConnectivity, + naviBbpsSessionHelper = naviBbpsSessionHelper, + fetchBillHandler = fetchBillHandler, + coinsSyncManager = coinsSyncManager, + zeroPlatformFeeBottomSheetViewTracker = zeroPlatformFeeBottomSheetViewTracker, + litmusExperimentsUseCase = litmusExperimentsUseCase, + naviBbpsConfigUseCase = naviBbpsConfigUseCase, + rewardsNudgeEntityFetchUseCase = rewardsNudgeEntityFetchUseCase, + payBillAmountChipsHelper = payBillAmountChipsHelper, + bbpsArcHelper = bbpsArcHelper, + notifyDuplicatePaymentHandler = notifyDuplicatePaymentHandler, + tStoreOrderHandler = tStoreOrderHandler, + resProvider = resProvider, + naviPaymentRewardEventBus = naviPaymentRewardEventBus, + ) + } + + @Test + fun `updatePaymentAmount should update payment amount state flow`() = runTest { + val testAmount = "100.00" + viewModel.updatePaymentAmount(testAmount) + assertEquals(testAmount, viewModel.paymentAmount.first()) + } + + @Test + fun `isPayButtonEnabled should be false when amount is empty`() = runTest { + viewModel.updatePaymentAmount("") + assertFalse(viewModel.isPayButtonEnabled.first()) + } + + @Test + fun `isPayButtonEnabled should be false when amount is zero`() = runTest { + viewModel.updatePaymentAmount("0") + assertFalse(viewModel.isPayButtonEnabled.first()) + } + + @Test + fun `isPayButtonEnabled should be false when amount is valid but bill details are null`() = + runTest { + viewModel.updatePaymentAmount("100.00") + assertFalse(viewModel.isPayButtonEnabled.first()) + } + + @Test + fun `onCreditCardPaymentOptionSelected should update selection and set amount for total option`() = + runTest { + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + val billDetailsEntity = + BillDetailsEntity( + billId = "bill_123", + billerId = "biller_456", + referenceId = "ref_789", + amount = "1000.00", + dueDate = "2023-12-31", + billDate = "2023-12-01", + billNumber = "BILL123", + accountHolderName = "Test User", + billerAdditionalParams = emptyList(), + billPeriod = "Dec 2023", + ) + + val testPayBillSource = + PayBillSource.Others( + billerDetailsEntity = mockk(relaxed = true), + billDetailsEntity = billDetailsEntity, + customerParams = mapOf(), + formattedLastPaidDate = "", + formattedLastPaidAmount = "", + billerId = "test_biller_id", + amount = "1000.00", + isConsentRequired = false, + ) + + every { savedStateHandle.get("payBillScreenSource") } returns + testPayBillSource + } + + @Test + fun `onCreditCardPaymentOptionSelected should update selection and set amount for minimum option`() = + runTest { + val billDetailsEntity = + BillDetailsEntity( + billId = "bill_123", + billerId = "biller_456", + referenceId = "ref_789", + amount = "1000.00", + dueDate = "2023-12-31", + billDate = "2023-12-01", + billNumber = "BILL123", + accountHolderName = "Test User", + billerAdditionalParams = + listOf( + BillerAdditionalParamsEntity( + paramName = "MinimumDueAmount", + value = "200.00", + displayName = "Minimum Due", + shouldDisplay = true, + displayOrder = 1, + ) + ), + billPeriod = "Dec 2023", + ) + + val testPayBillSource = + PayBillSource.Others( + billerDetailsEntity = mockk(relaxed = true), + billDetailsEntity = billDetailsEntity, + customerParams = mapOf(), + formattedLastPaidDate = "", + formattedLastPaidAmount = "", + billerId = "test_biller_id", + amount = "1000.00", + isConsentRequired = false, + ) + + every { savedStateHandle.get("payBillScreenSource") } returns + testPayBillSource + } + + @Test + fun `updateConsentProvided should update consent state`() = runTest { + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + // Start with a default value + viewModel.updateConsentProvided(false) + + // Verify initial state + assertEquals(false, viewModel.isConsentProvided.first()) + + // Call the method we want to test + viewModel.updateConsentProvided(true) + + // Verify updated state + assertEquals(true, viewModel.isConsentProvided.first()) + } + + @Test + fun `payment amount exactness should update error message ID`() = runTest { + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + viewModel.updatePaymentAmount("123.45") + + assertNotNull(viewModel.errorMessageId.value) + } + + @Test + fun `onPaymentResultReceived should handle failed payment`() = runTest { + viewModel.onPaymentResultReceived( + bbpsTransactionId = "", + paymentResponseMetadata = JSONObject(), + ) + + assertFalse(viewModel.isPayButtonClicked) + } + + @Test + fun `header state should be updatable`() = runTest { + val billerDetailsEntity = mockk(relaxed = true) + + val payBillSource = + PayBillSource.Others( + billerDetailsEntity = billerDetailsEntity, + billDetailsEntity = null, + customerParams = mapOf(), + formattedLastPaidDate = "", + formattedLastPaidAmount = "", + billerId = "test_biller_id", + amount = "100.00", + isConsentRequired = false, + ) + + every { savedStateHandle.get("payBillScreenSource") } returns payBillSource + + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + val testViewModel = + PayBillViewModel( + savedStateHandle = savedStateHandle, + dispatcherProvider = TestCoroutineDispatcherProvider(testDispatcher), + payBillRepository = payBillRepository, + naviBbpsDateUtils = naviBbpsDateUtils, + resourceProvider = resourceProvider, + billCategoriesRepository = billCategoriesRepository, + myBillsRepository = myBillsRepository, + naviBbpsCommonRepository = naviBbpsCommonRepository, + naviNetworkConnectivity = naviNetworkConnectivity, + naviBbpsSessionHelper = naviBbpsSessionHelper, + fetchBillHandler = fetchBillHandler, + coinsSyncManager = coinsSyncManager, + zeroPlatformFeeBottomSheetViewTracker = zeroPlatformFeeBottomSheetViewTracker, + litmusExperimentsUseCase = litmusExperimentsUseCase, + naviBbpsConfigUseCase = naviBbpsConfigUseCase, + rewardsNudgeEntityFetchUseCase = rewardsNudgeEntityFetchUseCase, + payBillAmountChipsHelper = payBillAmountChipsHelper, + bbpsArcHelper = bbpsArcHelper, + notifyDuplicatePaymentHandler = notifyDuplicatePaymentHandler, + tStoreOrderHandler = tStoreOrderHandler, + resProvider = resProvider, + naviPaymentRewardEventBus = naviPaymentRewardEventBus, + ) + + testDispatcher.scheduler.advanceUntilIdle() + + val expectedHeaderState = + PayBillHeaderState.OtherHeaders(billerDetailsEntity = billerDetailsEntity) + assertEquals(expectedHeaderState, testViewModel.payBillHeaderState.first()) + } + + @Test + fun `bill fetch failure should update error state`() = runTest { + coEvery { + fetchBillHandler.fetchBill( + payBillViewModel = any(), + isConsentProvided = any(), + onSuccess = any(), + onError = captureLambda(), + screenName = any(), + ) + } answers { lambda<() -> Unit>().invoke() } + } + + @Test + fun `bill fetch success should update UI state`() = runTest { + val billEntity = + BillDetailsEntity( + billId = "bill_123", + billerId = "biller_456", + referenceId = "ref_789", + amount = "1000.00", + dueDate = "2023-12-31", + billDate = "2023-12-01", + billNumber = "BILL123", + accountHolderName = "Test User", + billerAdditionalParams = emptyList(), + billPeriod = "Dec 2023", + ) + + coEvery { + fetchBillHandler.fetchBill( + payBillViewModel = any(), + isConsentProvided = any(), + onSuccess = captureLambda(), + onError = any(), + screenName = any(), + ) + } answers { lambda<(BillDetailsEntity, Boolean) -> Unit>().invoke(billEntity, false) } + setupBillDetailsEntity(billEntity) + } + + @Test + fun `should be able to auto focus on amount field`() = runTest { + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + val testViewModel = + PayBillViewModel( + savedStateHandle = savedStateHandle, + dispatcherProvider = TestCoroutineDispatcherProvider(testDispatcher), + payBillRepository = payBillRepository, + naviBbpsDateUtils = naviBbpsDateUtils, + resourceProvider = resourceProvider, + billCategoriesRepository = billCategoriesRepository, + myBillsRepository = myBillsRepository, + naviBbpsCommonRepository = naviBbpsCommonRepository, + naviNetworkConnectivity = naviNetworkConnectivity, + naviBbpsSessionHelper = naviBbpsSessionHelper, + fetchBillHandler = fetchBillHandler, + coinsSyncManager = coinsSyncManager, + zeroPlatformFeeBottomSheetViewTracker = zeroPlatformFeeBottomSheetViewTracker, + litmusExperimentsUseCase = litmusExperimentsUseCase, + naviBbpsConfigUseCase = naviBbpsConfigUseCase, + rewardsNudgeEntityFetchUseCase = rewardsNudgeEntityFetchUseCase, + payBillAmountChipsHelper = payBillAmountChipsHelper, + bbpsArcHelper = bbpsArcHelper, + notifyDuplicatePaymentHandler = notifyDuplicatePaymentHandler, + tStoreOrderHandler = tStoreOrderHandler, + resProvider = resProvider, + naviPaymentRewardEventBus = naviPaymentRewardEventBus, + ) + + val autoFocusField = testViewModel.javaClass.getDeclaredField("_shouldAutoFocusOnAmount") + autoFocusField.isAccessible = true + val stateFlow = autoFocusField.get(testViewModel) as MutableStateFlow + stateFlow.value = true + + assertTrue(testViewModel.shouldAutoFocusOnAmount.first()) + } + + @Test + fun `setting bill category entity should update related information`() = runTest { + val categoryEntity = + BillCategoryEntity( + categoryId = "1", + title = "Test Category", + iconUrl = "https://test.com/image.png", + searchBoxPlaceholderText = "Search billers", + billerListStateHeading = "Popular billers", + billerListAllHeading = "All billers", + reminderTitle = "Set reminder", + ) + + every { savedStateHandle.get("billCategoryEntity") } returns + categoryEntity + + val mockDefaultConfig = mockk(relaxed = true) + mockkStatic("com.navi.bbps.common.utils.ConfigExtKt") + coEvery { naviBbpsConfigUseCase.getDefaultConfig(any()) } returns mockDefaultConfig + + val testViewModel = + PayBillViewModel( + savedStateHandle = savedStateHandle, + dispatcherProvider = TestCoroutineDispatcherProvider(testDispatcher), + payBillRepository = payBillRepository, + naviBbpsDateUtils = naviBbpsDateUtils, + resourceProvider = resourceProvider, + billCategoriesRepository = billCategoriesRepository, + myBillsRepository = myBillsRepository, + naviBbpsCommonRepository = naviBbpsCommonRepository, + naviNetworkConnectivity = naviNetworkConnectivity, + naviBbpsSessionHelper = naviBbpsSessionHelper, + fetchBillHandler = fetchBillHandler, + coinsSyncManager = coinsSyncManager, + zeroPlatformFeeBottomSheetViewTracker = zeroPlatformFeeBottomSheetViewTracker, + litmusExperimentsUseCase = litmusExperimentsUseCase, + naviBbpsConfigUseCase = naviBbpsConfigUseCase, + rewardsNudgeEntityFetchUseCase = rewardsNudgeEntityFetchUseCase, + payBillAmountChipsHelper = payBillAmountChipsHelper, + bbpsArcHelper = bbpsArcHelper, + notifyDuplicatePaymentHandler = notifyDuplicatePaymentHandler, + tStoreOrderHandler = tStoreOrderHandler, + resProvider = resProvider, + naviPaymentRewardEventBus = naviPaymentRewardEventBus, + ) + + assertEquals(categoryEntity, testViewModel.billCategoryEntity) + } + + @Test + fun `should be able to update saved bill in repository`() = runTest { + val myBillEntity = mockk(relaxed = true) + coEvery { myBillsRepository.updateSavedBill(any()) } returns Unit + + myBillsRepository.updateSavedBill(myBillEntity) + + coVerify { myBillsRepository.updateSavedBill(myBillEntity) } + } + + private fun setupBillDetailsEntity(billDetailsEntity: BillDetailsEntity) { + val payBillSource = + PayBillSource.Others( + billerDetailsEntity = mockk(relaxed = true), + billDetailsEntity = billDetailsEntity, + customerParams = mapOf(), + formattedLastPaidDate = "", + formattedLastPaidAmount = "", + billerId = "test_biller_id", + amount = "100.00", + isConsentRequired = false, + ) + every { savedStateHandle.get("payBillScreenSource") } returns payBillSource + } +}