Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ipc 186 unit tests payment component #395

Merged
merged 4 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.gini.android.health.sdk.paymentcomponent

import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -22,7 +23,8 @@ class PaymentComponent(private val context: Context, private val giniHealth: Gin
MutableStateFlow<SelectedPaymentProviderAppState>(SelectedPaymentProviderAppState.NothingSelected)
val selectedPaymentProviderAppFlow: StateFlow<SelectedPaymentProviderAppState> = _selectedPaymentProviderAppFlow.asStateFlow()

private val paymentComponentPreferences = PaymentComponentPreferences(context)
@VisibleForTesting
internal val paymentComponentPreferences = PaymentComponentPreferences(context)

var listener: Listener? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.TypedValue
import androidx.annotation.ColorInt
import net.gini.android.health.api.models.PaymentProvider
import net.gini.android.health.sdk.util.extensions.generateBitmapDrawableIcon
import org.slf4j.LoggerFactory

internal const val Scheme = "ginipay" // It has to match the scheme in query tag in manifest
Expand Down Expand Up @@ -122,22 +119,7 @@ data class PaymentProviderApp(
}
return PaymentProviderApp(
name = paymentProvider.name,
icon = BitmapFactory.decodeByteArray(paymentProvider.icon, 0, paymentProvider.icon.size)
?.let { bitmap ->
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
ICON_SIZE,
context.resources.displayMetrics
).toInt()
val scaledBitamp = Bitmap.createScaledBitmap(
bitmap,
iconSizePx,
iconSizePx,
true
)
bitmap.recycle()
BitmapDrawable(context.resources, scaledBitamp)
},
icon = context.generateBitmapDrawableIcon(paymentProvider.icon, paymentProvider.icon.size),
colors = PaymentProviderAppColors(
backgroundColor = Color.parseColor("#${paymentProvider.colors.backgroundColorRGBHex}"),
textColor = Color.parseColor("#${paymentProvider.colors.textColoRGBHex}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.gini.android.health.sdk.util.extensions

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.util.TypedValue
import androidx.annotation.VisibleForTesting
import net.gini.android.health.sdk.paymentprovider.PaymentProviderApp

// In a future refactoring we can split extensions into files according to what component they extend
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Comment] Yes, thank you for adding this comment! I wholeheartedly agree! 👍 👍 👍

@VisibleForTesting
internal fun Context.generateBitmapDrawableIcon(icon: ByteArray, iconSize: Int): BitmapDrawable? {
return BitmapFactory.decodeByteArray(icon, 0, iconSize)
?.let { bitmap ->
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
PaymentProviderApp.ICON_SIZE,
this.resources.displayMetrics
).toInt()
val scaledBitmap = Bitmap.createScaledBitmap(
bitmap,
iconSizePx,
iconSizePx,
true
)
bitmap.recycle()
BitmapDrawable(this.resources, scaledBitmap)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package net.gini.android.health.sdk.paymentComponent

import android.content.Context
import android.content.pm.PackageManager
import android.util.DisplayMetrics
import androidx.test.core.app.ApplicationProvider
import android.content.res.Resources
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
Expand All @@ -19,29 +22,30 @@ import net.gini.android.health.api.HealthApiDocumentManager
import net.gini.android.health.api.models.PaymentProvider
import net.gini.android.health.sdk.GiniHealth
import net.gini.android.health.sdk.paymentcomponent.PaymentComponent
import net.gini.android.health.sdk.paymentcomponent.PaymentComponentPreferences
import net.gini.android.health.sdk.paymentcomponent.PaymentProviderAppsState
import net.gini.android.health.sdk.paymentcomponent.SelectedPaymentProviderAppState
import net.gini.android.health.sdk.paymentprovider.PaymentProviderApp
import net.gini.android.health.sdk.paymentprovider.PaymentProviderAppColors
import net.gini.android.health.sdk.paymentprovider.getPaymentProviderApps
import net.gini.android.health.sdk.review.ReviewConfiguration
import net.gini.android.health.sdk.review.ReviewFragment
import net.gini.android.health.sdk.test.ViewModelTestCoroutineRule
import net.gini.android.health.sdk.util.extensions.generateBitmapDrawableIcon
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith


@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class PaymentComponentTest {

@get:Rule
val testCoroutineRule = ViewModelTestCoroutineRule()

private lateinit var context: Context
private lateinit var packageManager: PackageManager
private lateinit var giniHealth: GiniHealth
private var context: Context? = null
private var giniHealth: GiniHealth? = null
private val giniHealthAPI: GiniHealthAPI = mockk(relaxed = true) { GiniHealthAPI::class.java }
private val documentManager: HealthApiDocumentManager = mockk { HealthApiDocumentManager::class.java }

Expand Down Expand Up @@ -84,7 +88,7 @@ class PaymentComponentTest {
playStoreUrl = ""
)

private val notInstalledPaymentProvider = PaymentProvider(
private val noPlayStoreUrlPaymentProvider = PaymentProvider(
id = "payment provider id 3",
name = "payment provider name",
packageName = "net.gini.android.bank.exampleapp3",
Expand All @@ -100,17 +104,50 @@ class PaymentComponentTest {
fun setUp() {
every { giniHealthAPI.documentManager } returns documentManager
giniHealth = GiniHealth(giniHealthAPI)
context = ApplicationProvider.getApplicationContext()
packageManager = mockk(relaxed = true)
context = getApplicationContext()
}

@After
fun tearDown() {
giniHealth = null
context = null
unmockkAll()
}

private fun createMockedContextAndSetDependencies(paymentProviderList: List<PaymentProvider>, paymentProviderAppsList: List<PaymentProviderApp>): Context {
val privateContext: Context = mockk()
val resources: Resources = spyk(context!!.resources)
val packageManager: PackageManager = mockk(relaxed = true)

mockkStatic(Context::generateBitmapDrawableIcon)
mockkStatic(PackageManager::getPaymentProviderApps)
every { privateContext.applicationContext } returns context
every { privateContext.packageManager } returns packageManager
every { privateContext.resources } returns resources
every { privateContext.generateBitmapDrawableIcon(any(), any()) } returns null
every { packageManager.getPaymentProviderApps(paymentProviderList, privateContext) } returns paymentProviderAppsList
Comment on lines +122 to +128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Comment] Thank you for this elegant solution! 👏


return privateContext
}

private fun buildPaymentProviderApp(paymentProvider: PaymentProvider, isInstalled: Boolean) = PaymentProviderApp(
name = paymentProvider.name,
icon = null,
colors = PaymentProviderAppColors(
backgroundColor = 0,
textColor = 0
),
paymentProvider = paymentProvider,
installedPaymentProviderApp = if (isInstalled) mockk(relaxed = true) else null
)

@Test
fun `emits error when it cannot load payment providers`() = runTest {
// Given
coEvery { documentManager.getPaymentProviders() } returns Resource.Error()

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -125,7 +162,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Cancelled()

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -146,7 +183,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -160,19 +197,87 @@ class PaymentComponentTest {
}

@Test
fun `emits only installed payment providers`() = runTest {
fun `doesn't emit payment providers which are not installed and which don't have a Play Store url`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider,
paymentProvider1,
paymentProvider2,
notInstalledPaymentProvider
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppList = listOf<PaymentProviderApp>(
buildPaymentProviderApp(paymentProvider, true),
buildPaymentProviderApp(paymentProvider1, true),
buildPaymentProviderApp(paymentProvider2, true),
buildPaymentProviderApp(noPlayStoreUrlPaymentProvider, false)
)
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
paymentComponent.paymentProviderAppsFlow.test {
val validation = awaitItem()
assertThat(validation).isInstanceOf(PaymentProviderAppsState.Success::class.java)
assertThat((validation as PaymentProviderAppsState.Success).paymentProviderApps.size).isEqualTo(3)

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `emits payment provider if it's not installed but has Play Store url`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppList = listOf(buildPaymentProviderApp(paymentProvider, false))
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppList)

//When
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
paymentComponent.paymentProviderAppsFlow.test {
val validation = awaitItem()
assertThat(validation).isInstanceOf(PaymentProviderAppsState.Success::class.java)
assertThat((validation as PaymentProviderAppsState.Success).paymentProviderApps.size).isEqualTo(1)

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `emits payment provider if it has Play Store URL and is installed`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider,
paymentProvider1,
paymentProvider2,
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppsList = listOf(
buildPaymentProviderApp(paymentProvider, true),
buildPaymentProviderApp(paymentProvider1, true),
buildPaymentProviderApp(paymentProvider2, true),
buildPaymentProviderApp(noPlayStoreUrlPaymentProvider, false)
)
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppsList)

//When
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -189,13 +294,13 @@ class PaymentComponentTest {
fun `returns empty list when no payment providers installed`() = runTest {
// Given
val paymentProviderList = listOf(
notInstalledPaymentProvider
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -218,7 +323,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand Down Expand Up @@ -247,7 +352,7 @@ class PaymentComponentTest {

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

paymentComponent.selectedPaymentProviderAppFlow.test {
Expand All @@ -271,7 +376,7 @@ class PaymentComponentTest {
fun `throws exception when trying to create ReviewFragment if no payment provider app is set`() = runTest {
// Given
val reviewConfiguration: ReviewConfiguration = mockk(relaxed = true)
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)

// When trying to instantiate fragment, then exception should be thrown
paymentComponent.getPaymentReviewFragment("", reviewConfiguration)
Expand All @@ -287,7 +392,7 @@ class PaymentComponentTest {
// When
every { paymentComponent.selectedPaymentProviderAppFlow } returns MutableStateFlow(SelectedPaymentProviderAppState.AppSelected(paymentProviderApp))

//Then
// Then
assertThat(paymentComponent.getPaymentReviewFragment("", reviewConfiguration)).isInstanceOf(ReviewFragment::class.java)
}

Expand Down
Loading
Loading