Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
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
7 changes: 7 additions & 0 deletions CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ An implementation of Backbase Observability that makes it easy to add 3rd party
\
LTS 24.09

## [sample/devs/O11Y-API](https://github.com/Backbase/golden-sample-app-android/tree/sample/devs/O11Y-API)
Extending Backbase Observability to capture API responses.
\
**Status**: Maintained
\
LTS 24.09

## [sample/devs/open-telemetry](https://github.com/Backbase/golden-sample-app-android/tree/sample/devs/open-telemetry)
An implementation of Backbase OpenTelemetry Connector to track screen views and user actions.
\
Expand Down
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ If you want to create your own samples check the [CONTRIBUTING.md](CONTRIBUTING.
## Discussion
You can discuss samples, request new examples and report problems on Github Discussions.

##
## 💻 Getting started
This project depends on various artifacts published to repositories on [Backbase Repo](https://repo.backbase.com). You must have read access to these repositories to build this project. The project also requires a specific keystore in order for the app to communicate with the backend.
In order to configure the project, please follow this [guide on Backbase.io](https://backbase.io/developers/documentation/mobile-devkit/getting-started/set-up-android-development/).
Expand All @@ -21,4 +20,36 @@ Note that this project connects to the EBP Sandbox Environment, for that you nee
In order to login you can find user credentials at [user-credentials page](https://backbase.io/ebp-sandbox/user-credentials?experience=retail)

## The Journey Architecture
Backbase mobile is built with the journey architecture, where a journey is an independent set of screens that form a typical user journey. To learn more about the Backbase journey architecture read this [guide](https://backbase.io/developers/documentation/retail-banking-universal/latest/system-wide/architecture/mobile-journey-architecture-understand/).
Backbase mobile is built with the journey architecture, where a journey is an independent set of screens that form a typical user journey. To learn more about the Backbase journey architecture read this [guide](https://backbase.io/developers/documentation/retail-banking-universal/latest/system-wide/architecture/mobile-journey-architecture-understand/).

## Tests
The project includes testing, demonstrating how to test the journey through unit tests, instrumented tests, and screenshot tests. It also highlights how to generate code coverage reports by using Jacoco.

Run Unit Tests
```sh
./gradlew testDebug
```

Run Instrumented and Screenshot tests
```sh
./gradlew accounts-journey:cAT
```

Run code coverage and root code coverage
```sh
./gradlew codeCoverageReport
./gradlew rootCodeCoverageReport
```

Run coverage verification and root coverage verification
```sh
./gradlew coverageVerification
./gradlew rootCoverageVerification
```

## Static code analysis
The project uses [Detekt](https://github.com/detekt/detekt) for identifying code smells and performance issues.
```sh
./gradlew detekt
./gradlew detektTest
```
28 changes: 18 additions & 10 deletions accounts-journey/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,42 @@ plugins {
id(backbase.plugins.feature.android.module.get().pluginId)
id(libs.plugins.kotlin.parcelize.get().pluginId)
id(backbase.plugins.configured.detekt.get().pluginId)
id(backbase.plugins.jacoco.codecoverage.get().pluginId)
id(libs.plugins.karumi.get().pluginId)
id(libs.plugins.navigation.safe.args.get().pluginId)
}

android {
namespace = "com.backbase.accounts_journey"
defaultConfig {
testApplicationId = "com.backbase.accounts_journey.test"
testInstrumentationRunner = "com.karumi.shot.ShotTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
testInstrumentationRunnerArguments["useTestStorageService"] = "true"
}
buildTypes {
debug {
enableAndroidTestCoverage = true
}
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
}

dependencies {
implementation(libs.bundles.navigation)

androidTestImplementation(libs.navigation.testing)
testImplementation(projects.testData)

// Backbase libraries
implementation(platform(backbase.bom))
implementation(backbase.bundles.common)
implementation(midTier.bundles.common)
implementation(libs.bundles.navigation)

testImplementation(libs.archCore)
androidTestImplementation(projects.fakeAccountsUseCase)
androidTestImplementation(libs.archCore)
androidTestImplementation(libs.navigation.testing)
androidTestImplementation(libs.koinTest)
androidTestImplementation(libs.coroutines)
androidTestImplementation(libs.coroutinesTest)
androidTestImplementation(libs.testParameterInjector)

androidTestImplementation(projects.fakeAccountsUseCase)
androidTestImplementation(libs.bundles.test.instrumented)

androidTestUtil(libs.orchestrator)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.backbase.accounts_journey.generator

import com.backbase.accounts_journey.domain.model.account_detail.AccountDetail
import com.backbase.android.test_data.StringGenerator

/**
* A random AccountDetail generator. It is not fully implemented, but it gives you an idea how to randomize the data.
*/
object AccountDetailGenerator {

fun generateAccountDetail(
id: String = StringGenerator.randomString(),
productId: String = StringGenerator.randomString(),
currency: String = StringGenerator.generateRandomCurrency()
): AccountDetail {
return AccountDetail {
this.id = id
this.productId = productId
this.currency = currency
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.backbase.accounts_journey.generator

import com.backbase.accounts_journey.domain.model.account_summary.AccountSummary
import com.backbase.accounts_journey.domain.model.account_summary.MaskableAttribute
import com.backbase.accounts_journey.domain.model.account_summary.UserPreferences
import com.backbase.accounts_journey.domain.model.account_summary.current_accounts.CurrentAccount
import com.backbase.accounts_journey.domain.model.account_summary.current_accounts.CurrentAccounts
import com.backbase.accounts_journey.domain.model.common.ProductState
import com.backbase.android.test_data.NumberGenerator.randomFloat
import com.backbase.android.test_data.StringGenerator.generateRandomBBAN
import com.backbase.android.test_data.StringGenerator.generateRandomBIC
import com.backbase.android.test_data.StringGenerator.generateRandomCurrency
import com.backbase.android.test_data.StringGenerator.randomString
import java.math.BigDecimal
import java.time.OffsetDateTime
import java.time.ZoneOffset
import kotlin.random.Random

/**
* A random AccountSummary generator. It is not fully implemented, but it gives you an idea how to randomize the data.
*/
object AccountSummaryGenerator {

fun generateAccountSummary(
id: String = randomString(),
displayName: String = randomString()
): AccountSummary {
return AccountSummary {
currentAccounts = CurrentAccounts {
products = listOf(
CurrentAccount {
debitCardItems = emptySet()
bookedBalance = randomFloat().toString()
availableBalance = randomFloat(1, 100).toString()
creditLimit = randomFloat().toString()
BBAN = generateRandomBBAN()
BIC = generateRandomBIC()
unMaskableAttributes = setOf(MaskableAttribute.BBAN)
currency = generateRandomCurrency()
bankBranchCode = Random.nextInt(10000, 99999).toString()
bankBranchCode2 = null
accountInterestRate = BigDecimal.ONE
creditLimitUsage = BigDecimal.TEN
creditLimitInterestRate = BigDecimal.ONE
creditLimitExpiryDate =
OffsetDateTime.of(2029, 12, 21, 0, 0, 0, 0, ZoneOffset.UTC)
accruedInterest = BigDecimal.ZERO
accountHolderNames = "Paolo Doe"
startDate = OffsetDateTime.now()
creditAccount = true
debitAccount = true
this.id = id
name = "TESTDATA"
externalTransferAllowed = true
crossCurrencyAllowed = true
productKindName = "Current Account"
productTypeName = "Current Account"
bankAlias = "Paolo's Current Account"
accountOpeningDate = OffsetDateTime.now()
lastUpdateDate = OffsetDateTime.now()
userPreferences = UserPreferences {
alias = "Paolo’s Current Test"
visible = true
favorite = false
}
state = ProductState {
externalStateId = "Active"
state = "Active"
}
this.displayName = displayName
}
)
name = "Current Accounts"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.backbase.accounts_journey.presentation.accountdetail.ui

import com.backbase.accounts_journey.common.FailedGetDataException
import com.backbase.accounts_journey.data.usecase.AccountDetailUseCase
import com.backbase.accounts_journey.data.usecase.Params
import com.backbase.accounts_journey.generator.AccountDetailGenerator.generateAccountDetail
import com.backbase.accounts_journey.presentation.accountdetail.mapper.AccountDetailUiMapper
import com.backbase.android.test_data.CoroutineTest
import com.backbase.android.test_data.StringGenerator
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.BDDAssertions.then
import org.junit.jupiter.api.Test

@OptIn(ExperimentalCoroutinesApi::class)
class AccountDetailViewModelTest : CoroutineTest {

override lateinit var testScope: TestScope
override lateinit var testDispatcherProvider: TestDispatcher

private val accountDetailUseCase: AccountDetailUseCase = mockk()

private val viewModel by lazy {
AccountDetailViewModel(
useCase = accountDetailUseCase,
mapper = AccountDetailUiMapper(mockk(relaxed = true)),
defaultDispatcher = testDispatcherProvider
)
}

@Test
fun `should get account detail when success`() = runTest {
val id = StringGenerator.randomString()
val params = Params({ this.id = id })
coEvery {
accountDetailUseCase.getAccountDetail(params)
} returns Result.success(generateAccountDetail(id = id))

viewModel.onEvent(AccountDetailEvent.OnGetAccountDetail(id))

val uiState = viewModel.uiState.value
then(uiState.accountDetail?.id).isEqualTo(id)
}

@Test
fun `should get error detail when failed`() = runTest {
coEvery {
accountDetailUseCase.getAccountDetail(any())
} returns Result.failure(FailedGetDataException())

viewModel.onEvent(AccountDetailEvent.OnGetAccountDetail(""))

val uiState = viewModel.uiState.value
then(uiState.error).isNotNull
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.backbase.accounts_journey.presentation.accountlist.ui

import com.backbase.accounts_journey.common.FailedGetDataException
import com.backbase.accounts_journey.data.usecase.AccountsUseCase
import com.backbase.accounts_journey.generator.AccountSummaryGenerator
import com.backbase.accounts_journey.presentation.accountlist.mapper.AccountUiMapper
import com.backbase.android.test_data.CoroutineTest
import com.backbase.android.test_data.StringGenerator.randomString
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.BDDAssertions.then
import org.junit.jupiter.api.Test

@OptIn(ExperimentalCoroutinesApi::class)
class AccountListViewModelTest : CoroutineTest {

override lateinit var testScope: TestScope
override lateinit var testDispatcherProvider: TestDispatcher

private val accountsUseCase: AccountsUseCase = mockk()

private val viewModel by lazy {
AccountListViewModel(
useCase = accountsUseCase,
mapper = AccountUiMapper(mockk(relaxed = true)),
defaultDispatcher = testDispatcherProvider
)
}

@Test
fun `should get account summary when success`() = runTest {
val accountSummary = AccountSummaryGenerator.generateAccountSummary()
coEvery {
accountsUseCase.getAccountSummary(true)
} returns Result.success(accountSummary)

viewModel.onEvent(AccountListEvent.OnGetAccounts)

val uiState = viewModel.uiState.value
then(uiState.accountSummary).isNotEmpty
}

@Test
fun `should get error when failed`() = runTest {
coEvery { accountsUseCase.getAccountSummary(true) } returns Result.failure(
FailedGetDataException()
)

viewModel.onEvent(AccountListEvent.OnGetAccounts)

val uiState = viewModel.uiState.value
then(uiState.error).isNotNull
}

@Test
fun `should get account summary when refresh`() = runTest {
val accountSummary = AccountSummaryGenerator.generateAccountSummary()
coEvery {
accountsUseCase.getAccountSummary(false)
} returns Result.success(accountSummary)

viewModel.onEvent(AccountListEvent.OnRefresh)

val uiState = viewModel.uiState.value
then(uiState.accountSummary).isNotEmpty
}

@Test
fun `should get account summary when search`() = runTest {
val query = randomString()
val accountSummary = AccountSummaryGenerator.generateAccountSummary(displayName = query)
coEvery {
accountsUseCase.getAccountSummary()
} returns Result.success(accountSummary)

viewModel.onEvent(AccountListEvent.OnSearch(query))

val uiState = viewModel.uiState.value
// Header + List Item = 2
then(uiState.accountSummary.size).isEqualTo(2)
}

@Test
fun `should get empty account summary when nothing found`() = runTest {
val query = randomString()
val accountSummary = AccountSummaryGenerator.generateAccountSummary(displayName = query)
coEvery {
accountsUseCase.getAccountSummary()
} returns Result.success(accountSummary)

viewModel.onEvent(AccountListEvent.OnSearch(randomString()))

val uiState = viewModel.uiState.value
then(uiState.accountSummary.size).isZero
}
}
4 changes: 3 additions & 1 deletion accounts-use-case/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id(backbase.plugins.base.android.library.module.get().pluginId)
id(backbase.plugins.configured.detekt.get().pluginId)
id(backbase.plugins.jacoco.codecoverage.get().pluginId)
}

android {
Expand All @@ -9,8 +10,9 @@ android {

dependencies {
implementation(projects.accountsJourney)
testImplementation(projects.testData)

// Backbase libraries
implementation(platform(backbase.bom))
implementation(backbase.gen.arrangements.client)
implementation(clients.arrangements)
}
Loading