diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetActivity.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetActivity.kt index 18b9083f5f4..a2da71d0b30 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetActivity.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetActivity.kt @@ -113,9 +113,11 @@ internal class FinancialConnectionsSheetActivity : AppCompatActivity() { ) is FinishWithResult -> { - viewEffect.finishToast?.let { + viewEffect.finishToast?.let { resId -> Toast.makeText( - this@FinancialConnectionsSheetActivity, it, Toast.LENGTH_LONG + this@FinancialConnectionsSheetActivity, + resId, + Toast.LENGTH_LONG ).show() } finishWithResult(viewEffect.result) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/CoroutineTestRule.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/CoroutineTestRule.kt new file mode 100644 index 00000000000..fdf557caa3f --- /dev/null +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/CoroutineTestRule.kt @@ -0,0 +1,25 @@ +package com.stripe.android.financialconnections + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@ExperimentalCoroutinesApi +class CoroutineTestRule( + private val testDispatcher: TestDispatcher, +) : TestWatcher() { + + override fun starting(description: Description) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + super.finished(description) + } +} diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt index 9206bbc1a30..811e24970dc 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt @@ -2,9 +2,8 @@ package com.stripe.android.financialconnections import android.content.Intent import android.net.Uri +import androidx.lifecycle.SavedStateHandle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.airbnb.mvrx.test.MavericksTestRule -import com.airbnb.mvrx.withState import com.google.common.truth.Truth.assertThat import com.stripe.android.core.Logger import com.stripe.android.core.exception.APIException @@ -34,6 +33,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.eq @@ -47,7 +47,7 @@ import org.mockito.kotlin.whenever class FinancialConnectionsSheetViewModelTest { @get:Rule - val mavericksRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) + val rule: TestRule = CoroutineTestRule(UnconfinedTestDispatcher()) private val eventReporter = mock() private val configuration = FinancialConnectionsSheet.Configuration( @@ -65,7 +65,8 @@ class FinancialConnectionsSheetViewModelTest { private val synchronizeFinancialConnectionsSession = mock() private val defaultInitialState = FinancialConnectionsSheetState( - FinancialConnectionsSheetActivityArgs.ForData(configuration) + args = FinancialConnectionsSheetActivityArgs.ForData(configuration), + savedState = null ) @Test @@ -105,7 +106,7 @@ class FinancialConnectionsSheetViewModelTest { val viewModel = createViewModel(defaultInitialState) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) require(it.viewEffect is FinishWithResult) require(it.viewEffect.result is Failed) @@ -160,7 +161,7 @@ class FinancialConnectionsSheetViewModelTest { ) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo( @@ -188,7 +189,7 @@ class FinancialConnectionsSheetViewModelTest { ) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isInstanceOf(Failed::class.java) } @@ -232,8 +233,7 @@ class FinancialConnectionsSheetViewModelTest { // end auth flow viewModel.handleOnNewIntent(cancelIntent()) - // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) assertThat(it.viewEffect).isEqualTo(FinishWithResult(Canceled)) } @@ -260,7 +260,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(cancelIntent()) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { require(it.viewEffect is FinishWithResult) require(it.viewEffect.result is Failed) assertThat(it.viewEffect.result.error).isInstanceOf(CustomManualEntryRequiredError::class.java) @@ -281,7 +281,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(errorIntent) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isInstanceOf(Failed::class.java) @@ -304,7 +304,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(successIntent()) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo( @@ -331,7 +331,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(Intent().apply { data = Uri.parse(nativeRedirectUrl) }) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.INTERMEDIATE_DEEPLINK) val viewEffect = it.viewEffect as OpenAuthFlowWithUrl assertThat(viewEffect.url).isEqualTo(aggregatorUrl) @@ -357,7 +357,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(Intent().apply { data = Uri.parse(returnUrl) }) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.INTERMEDIATE_DEEPLINK) val viewEffect = it.viewEffect as OpenAuthFlowWithUrl assertThat(viewEffect.url).isEqualTo( @@ -381,7 +381,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(successIntent()) // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Failed(apiException)) @@ -402,7 +402,7 @@ class FinancialConnectionsSheetViewModelTest { // Then // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Failed(APIException())) @@ -429,7 +429,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.onResume() // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.ON_EXTERNAL_ACTIVITY) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Canceled) @@ -456,7 +456,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.onBrowserActivityResult() // Then - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.ON_EXTERNAL_ACTIVITY) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Canceled) @@ -476,7 +476,7 @@ class FinancialConnectionsSheetViewModelTest { val viewModel = createViewModel(defaultInitialState) // Then - withState(viewModel) { assertThat(it.manifest).isEqualTo(syncResponse.manifest) } + assertThat(viewModel.stateFlow.value.manifest).isEqualTo(syncResponse.manifest) } } @@ -517,6 +517,7 @@ class FinancialConnectionsSheetViewModelTest { nativeRouter = mock(), analyticsTracker = analyticsTracker, browserManager = browserManager, + savedStateHandle = SavedStateHandle(), logger = Logger.noop() ) } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt index 475f85539ee..fa8de8eb624 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt @@ -1,12 +1,11 @@ package com.stripe.android.financialconnections.features.institutionpicker -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.test.MavericksTestRule -import com.airbnb.mvrx.withState import com.stripe.android.core.Logger import com.stripe.android.financialconnections.ApiKeyFixtures +import com.stripe.android.financialconnections.CoroutineTestRule import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.TestFinancialConnectionsAnalyticsTracker +import com.stripe.android.financialconnections.core.Result import com.stripe.android.financialconnections.domain.FeaturedInstitutions import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.PostAuthorizationSession @@ -22,10 +21,12 @@ import com.stripe.android.financialconnections.navigation.Destination import com.stripe.android.financialconnections.utils.TestHandleError import com.stripe.android.financialconnections.utils.TestNavigationManager import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verifyNoInteractions @@ -38,7 +39,7 @@ import kotlin.test.assertTrue internal class InstitutionPickerViewModelTest { @get:Rule - val mavericksTestRule = MavericksTestRule() + val rule: TestRule = CoroutineTestRule(UnconfinedTestDispatcher()) private val searchInstitutions = mock() private val featuredInstitutions = mock() @@ -92,9 +93,9 @@ internal class InstitutionPickerViewModelTest { val viewModel = buildViewModel(InstitutionPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertEquals(state.payload()!!.featuredInstitutions, institutionResponse) - assertIs(state.searchInstitutions) + assertIs(state.searchInstitutions) } } @@ -108,10 +109,8 @@ internal class InstitutionPickerViewModelTest { val viewModel = buildViewModel(InstitutionPickerState()) - withState(viewModel) { state -> - // payload with empty list - assertTrue(state.payload()!!.featuredInstitutions.data.isEmpty()) - } + // payload with empty list + assertTrue(viewModel.stateFlow.value.payload()!!.featuredInstitutions.data.isEmpty()) } @Test @@ -121,7 +120,7 @@ internal class InstitutionPickerViewModelTest { val viewModel = buildViewModel(InstitutionPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertTrue(state.payload() == null) handleError.assertError( error = error, @@ -170,7 +169,7 @@ internal class InstitutionPickerViewModelTest { viewModel.onQueryChanged(query) advanceUntilIdle() - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertEquals(state.payload()!!.featuredInstitutions, featuredResults) assertEquals(state.searchInstitutions()!!, searchResults) eventTracker.assertContainsEvent( @@ -193,7 +192,7 @@ internal class InstitutionPickerViewModelTest { viewModel.onQueryChanged(query) advanceUntilIdle() - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> verifyNoInteractions(searchInstitutions) assertTrue(eventTracker.sentEvents.none { it.eventName == "linked_accounts.search.succeeded" }) assertEquals(state.searchInstitutions()!!.data, emptyList()) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeStateTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeStateTest.kt index 166f1c60cb2..af092b6a84a 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeStateTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeStateTest.kt @@ -24,7 +24,8 @@ internal class FinancialConnectionsSheetNativeStateTest { reducedManualEntryProminenceInErrors = false, merchantLogos = emptyList() ) - ) + ), + savedState = null ).reducedBranding, ).isTrue() } @@ -39,7 +40,8 @@ internal class FinancialConnectionsSheetNativeStateTest { reducedManualEntryProminenceInErrors = false, merchantLogos = emptyList() ) - ) + ), + savedState = null ).reducedBranding, ).isFalse() } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt index cb64b9551f3..209703dfe22 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt @@ -2,13 +2,13 @@ package com.stripe.android.financialconnections.presentation import android.content.Intent import android.net.Uri +import androidx.lifecycle.SavedStateHandle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.airbnb.mvrx.test.MavericksTestRule -import com.airbnb.mvrx.withState import com.google.common.truth.Truth.assertThat import com.stripe.android.core.Logger import com.stripe.android.financialconnections.ApiKeyFixtures import com.stripe.android.financialconnections.ApiKeyFixtures.financialConnectionsSessionNoAccounts +import com.stripe.android.financialconnections.CoroutineTestRule import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent @@ -38,6 +38,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull @@ -50,7 +51,7 @@ import kotlin.test.assertIs internal class FinancialConnectionsSheetNativeViewModelTest { @get:Rule - val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) + val rule: TestRule = CoroutineTestRule(UnconfinedTestDispatcher()) private val nativeAuthFlowCoordinator = mock() private val completeFinancialConnectionsSession = mock() @@ -87,7 +88,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(EarlyTerminationCause.USER_INITIATED_WITH_CUSTOM_MANUAL_ENTRY)) - withState(viewModel) { + viewModel.stateFlow.value.let { require(it.viewEffect is Finish) require(it.viewEffect.result is Failed) assertThat(it.viewEffect.result.error).isInstanceOf(CustomManualEntryRequiredError::class.java) @@ -107,7 +108,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - withState(viewModel) { + viewModel.stateFlow.value.let { require(it.viewEffect is Finish) require(it.viewEffect.result is Completed) } @@ -133,7 +134,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - withState(viewModel) { + viewModel.stateFlow.value.let { require(it.viewEffect is Finish) require(it.viewEffect.result is Canceled) } @@ -165,7 +166,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - withState(viewModel) { + viewModel.stateFlow.value.let { require(it.viewEffect is Finish) require(it.viewEffect.result is Failed) } @@ -186,7 +187,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=success") viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { assertThat(it.webAuthFlow).isEqualTo(WebAuthFlowState.Success(intent.data!!.toString())) } } @@ -201,7 +202,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) assertThat(webAuthFlow.reason).isEqualTo(errorReason) @@ -217,7 +218,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -232,7 +233,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -245,7 +246,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=unknown") viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -258,7 +259,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=cancel") viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -271,7 +272,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/other-app-id?code=success") viewModel.handleOnNewIntent(intent) - withState(viewModel) { + viewModel.stateFlow.value.let { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -286,10 +287,11 @@ internal class FinancialConnectionsSheetNativeViewModelTest { private fun createViewModel( initialState: FinancialConnectionsSheetNativeState = FinancialConnectionsSheetNativeState( - FinancialConnectionsSheetNativeActivityArgs( + args = FinancialConnectionsSheetNativeActivityArgs( configuration = configuration, initialSyncResponse = ApiKeyFixtures.syncResponse(), - ) + ), + savedState = null ) ) = FinancialConnectionsSheetNativeViewModel( eventTracker = mock(), @@ -300,6 +302,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { nativeAuthFlowCoordinator = nativeAuthFlowCoordinator, logger = mock(), navigationManager = TestNavigationManager(), + savedStateHandle = SavedStateHandle(), initialState = initialState ) }