Skip to content

Commit

Permalink
Merge pull request #1878 from Adyen/feature/pay-by-bank-us-test
Browse files Browse the repository at this point in the history
Pay by Bank US - Component & Delegate Tests
  • Loading branch information
ozgur00 authored Nov 12, 2024
2 parents 20f9c34 + 8285e3d commit ddee485
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2024 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ozgur on 5/11/2024.
*/

package com.adyen.checkout.paybybankus

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent
import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate
import com.adyen.checkout.components.core.internal.ComponentEventHandler
import com.adyen.checkout.components.core.internal.PaymentComponentEvent
import com.adyen.checkout.paybybankus.internal.PayByBankUSComponentViewType
import com.adyen.checkout.paybybankus.internal.PayByBankUSDelegate
import com.adyen.checkout.test.LoggingExtension
import com.adyen.checkout.test.TestDispatcherExtension
import com.adyen.checkout.test.extensions.invokeOnCleared
import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@ExtendWith(MockitoExtension::class, TestDispatcherExtension::class, LoggingExtension::class)
internal class PayByBankUSComponentTest(
@Mock private val payByBankUSDelegate: PayByBankUSDelegate,
@Mock private val genericActionDelegate: GenericActionDelegate,
@Mock private val actionHandlingComponent: DefaultActionHandlingComponent,
@Mock private val componentEventHandler: ComponentEventHandler<PayByBankUSComponentState>,
) {

private lateinit var component: PayByBankUSComponent

@BeforeEach
fun before() {
whenever(payByBankUSDelegate.viewFlow) doReturn MutableStateFlow(PayByBankUSComponentViewType)
whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null)

component = PayByBankUSComponent(
payByBankUSDelegate,
genericActionDelegate,
actionHandlingComponent,
componentEventHandler,
)
}

@Test
fun `when component is created then delegates are initialized`() {
verify(payByBankUSDelegate).initialize(component.viewModelScope)
verify(genericActionDelegate).initialize(component.viewModelScope)
verify(componentEventHandler).initialize(component.viewModelScope)
}

@Test
fun `when component is cleared then delegates are cleared`() {
component.invokeOnCleared()

verify(payByBankUSDelegate).onCleared()
verify(genericActionDelegate).onCleared()
verify(componentEventHandler).onCleared()
}

@Test
fun `when observe is called then observe in delegates is called`() {
val lifecycleOwner = mock<LifecycleOwner>()
val callback: (PaymentComponentEvent<PayByBankUSComponentState>) -> Unit = {}

component.observe(lifecycleOwner, callback)

verify(payByBankUSDelegate).observe(lifecycleOwner, component.viewModelScope, callback)
verify(genericActionDelegate).observe(eq(lifecycleOwner), eq(component.viewModelScope), any())
}

@Test
fun `when removeObserver is called then removeObserver in delegates is called`() {
component.removeObserver()

verify(payByBankUSDelegate).removeObserver()
verify(genericActionDelegate).removeObserver()
}

@Test
fun `when component is initialized then view flow should match pay by bank delegate view flow`() = runTest {
component.viewFlow.test {
assertEquals(PayByBankUSComponentViewType, awaitItem())
expectNoEvents()
}
}

@Test
fun `when pay by bank delegate view flow emits a value then component view flow should match that value`() =
runTest {
val payByBankDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1)
whenever(payByBankUSDelegate.viewFlow) doReturn payByBankDelegateViewFlow
component = PayByBankUSComponent(
payByBankUSDelegate,
genericActionDelegate,
actionHandlingComponent,
componentEventHandler,
)

component.viewFlow.test {
assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem())

payByBankDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2)
assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem())

expectNoEvents()
}
}

@Test
fun `when action delegate view flow emits a value then component view flow should match that value`() = runTest {
val actionDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1)
whenever(genericActionDelegate.viewFlow) doReturn actionDelegateViewFlow
component = PayByBankUSComponent(
payByBankUSDelegate,
genericActionDelegate,
actionHandlingComponent,
componentEventHandler
)

component.viewFlow.test {
// this value should match the value of the main delegate and not the action delegate
// and in practice the initial value of the action delegate view flow is always null so it should be ignored
assertEquals(PayByBankUSComponentViewType, awaitItem())

actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2)
assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem())

expectNoEvents()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2024 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ozgur on 5/11/2024.
*/

package com.adyen.checkout.paybybankus.internal.ui

import app.cash.turbine.test
import com.adyen.checkout.components.core.Amount
import com.adyen.checkout.components.core.CheckoutConfiguration
import com.adyen.checkout.components.core.OrderRequest
import com.adyen.checkout.components.core.PaymentMethod
import com.adyen.checkout.components.core.internal.PaymentObserverRepository
import com.adyen.checkout.components.core.internal.analytics.GenericEvents
import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager
import com.adyen.checkout.components.core.internal.ui.model.ButtonComponentParamsMapper
import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper
import com.adyen.checkout.core.Environment
import com.adyen.checkout.paybybankus.PayByBankUSComponentState
import com.adyen.checkout.paybybankus.getPayByBankUSConfiguration
import com.adyen.checkout.paybybankus.internal.DefaultPayByBankUSDelegate
import com.adyen.checkout.test.LoggingExtension
import com.adyen.checkout.ui.core.internal.ui.SubmitHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.verify
import java.util.Locale

@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(MockitoExtension::class, LoggingExtension::class)
class DefaultPayByBankUSDelegateTest(
@Mock private val submitHandler: SubmitHandler<PayByBankUSComponentState>,
) {

private lateinit var analyticsManager: TestAnalyticsManager
private lateinit var delegate: DefaultPayByBankUSDelegate

@BeforeEach
fun before() {
analyticsManager = TestAnalyticsManager()
delegate = createPayByBankUSDelegate()
}

@Test
fun `when subscribed then component state flow should propagate a valid state`() = runTest {
delegate.componentStateFlow.test {
with(awaitItem()) {
assertEquals(TEST_PAYMENT_METHOD_TYPE, data.paymentMethod?.type)
assertEquals(TEST_ORDER, data.order)
assertTrue(isInputValid)
assertTrue(isValid)
}

cancelAndIgnoreRemainingEvents()
}
}

@ParameterizedTest
@MethodSource("amountSource")
fun `given component state is valid always amount is propagated in component state if set`(
configurationValue: Amount?,
expectedComponentStateValue: Amount?,
) = runTest {
if (configurationValue != null) {
val configuration = createCheckoutConfiguration(configurationValue)
delegate = createPayByBankUSDelegate(configuration = configuration)
}
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))
delegate.componentStateFlow.test {
assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount)
}
}

@Nested
inner class SubmitHandlerTest {

@Test
fun `when delegate is initialized then submit handler event is initialized`() = runTest {
val coroutineScope = CoroutineScope(UnconfinedTestDispatcher())
delegate.initialize(coroutineScope)
verify(submitHandler).initialize(coroutineScope, delegate.componentStateFlow)
}

@Test
fun `when delegate setInteractionBlocked is called then submit handler setInteractionBlocked is called`() =
runTest {
delegate.setInteractionBlocked(true)
verify(submitHandler).setInteractionBlocked(true)
}

@Test
fun `when delegate onSubmit is called then submit handler onSubmit is called`() = runTest {
delegate.componentStateFlow.test {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))
delegate.onSubmit()
verify(submitHandler).onSubmit(expectMostRecentItem())
}
}
}

@Nested
inner class AnalyticsTest {

@Test
fun `when delegate is initialized then analytics manager is initialized`() {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

analyticsManager.assertIsInitialized()
}

@Test
fun `when delegate is initialized, then render event is tracked`() {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

val expectedEvent = GenericEvents.rendered(TEST_PAYMENT_METHOD_TYPE)
analyticsManager.assertLastEventEquals(expectedEvent)
}

@Test
fun `when onSubmit is called, then submit event is tracked`() {
delegate.onSubmit()

val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE)
analyticsManager.assertLastEventEquals(expectedEvent)
}

@Test
fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest {
analyticsManager.setCheckoutAttemptId(TEST_CHECKOUT_ATTEMPT_ID)

delegate = createPayByBankUSDelegate()

delegate.componentStateFlow.test {
assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId)
}
}

@Test
fun `when delegate is cleared then analytics manager is cleared`() {
delegate.onCleared()

analyticsManager.assertIsCleared()
}
}

private fun createPayByBankUSDelegate(
configuration: CheckoutConfiguration = createCheckoutConfiguration(),
): DefaultPayByBankUSDelegate {
return DefaultPayByBankUSDelegate(
observerRepository = PaymentObserverRepository(),
paymentMethod = PaymentMethod(TEST_PAYMENT_METHOD_TYPE),
order = TEST_ORDER,
componentParams = ButtonComponentParamsMapper(CommonComponentParamsMapper()).mapToParams(
checkoutConfiguration = configuration,
deviceLocale = Locale.US,
dropInOverrideParams = null,
componentSessionParams = null,
componentConfiguration = configuration.getPayByBankUSConfiguration(),
),
analyticsManager = analyticsManager,
submitHandler = submitHandler,
)
}

private fun createCheckoutConfiguration(
amount: Amount? = null,
) = CheckoutConfiguration(
shopperLocale = Locale.US,
environment = Environment.TEST,
clientKey = TEST_CLIENT_KEY,
amount = amount,
)

companion object {
private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty"
private const val TEST_PAYMENT_METHOD_TYPE = "TEST_PAYMENT_METHOD_TYPE"
private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA")
private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID"

@JvmStatic
fun amountSource() = listOf(
// configurationValue, expectedComponentStateValue
arguments(Amount("EUR", 100), Amount("EUR", 100)),
arguments(Amount("USD", 0), Amount("USD", 0)),
arguments(null, null),
)
}
}

0 comments on commit ddee485

Please sign in to comment.