From 5d95fc7ccbec806453ca06ee11aa7e422e380977 Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Mon, 30 Jan 2023 15:24:42 +0700 Subject: [PATCH 1/7] [#74] Create HomeScreenUITest --- app/build.gradle.kts | 16 +- .../compose/crypto/test/CoroutineTestRule.kt | 38 ++++ .../nimblehq/compose/crypto/test/MockUtil.kt | 100 ++++++++++ .../compose/crypto/ui/BaseScreenTest.kt | 21 ++ .../crypto/ui/screen/HomeScreenUITest.kt | 185 ++++++++++++++++++ .../crypto/ui/screens/home/HomeViewModel.kt | 4 +- buildSrc/src/main/java/Versions.kt | 2 +- 7 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1229e7b5..734add5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,7 @@ android { targetSdk = Versions.ANDROID_TARGET_SDK_VERSION versionCode = Versions.ANDROID_VERSION_CODE versionName = Versions.ANDROID_VERSION_NAME + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -103,6 +104,12 @@ android { xmlOutput = file("build/reports/lint/lint-result.xml") } + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } + testOptions { unitTests { isIncludeAndroidResources = true @@ -153,6 +160,11 @@ dependencies { debugImplementation("androidx.compose.ui:ui-tooling:${Versions.COMPOSE_VERSION}") // Testing + androidTestImplementation("androidx.compose.ui:ui-test-junit4:${Versions.COMPOSE_VERSION}") + androidTestImplementation("io.mockk:mockk-android:${Versions.TEST_MOCKK_VERSION}") + androidTestImplementation("io.mockk:mockk-agent-android:${Versions.TEST_MOCKK_VERSION}") + androidTestImplementation("io.kotest:kotest-assertions-core:${Versions.TEST_KOTEST_VERSION}") + testImplementation("io.kotest:kotest-assertions-core:${Versions.TEST_KOTEST_VERSION}") testImplementation("junit:junit:${Versions.TEST_JUNIT_VERSION}") testImplementation("androidx.test:core:${Versions.TEST_ANDROIDX_CORE_VERSION}") @@ -164,8 +176,8 @@ dependencies { testImplementation("io.mockk:mockk:${Versions.TEST_MOCKK_VERSION}") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.TEST_COROUTINES_VERSION}") testImplementation("app.cash.turbine:turbine:${Versions.TEST_TURBINE_VERSION}") - testImplementation ("androidx.compose.ui:ui-test-junit4:${Versions.COMPOSE_VERSION}") - testImplementation ("org.robolectric:robolectric:${Versions.TEST_ROBOLECTRIC_VERSION}") + testImplementation("androidx.compose.ui:ui-test-junit4:${Versions.COMPOSE_VERSION}") + testImplementation("org.robolectric:robolectric:${Versions.TEST_ROBOLECTRIC_VERSION}") kaptTest("com.google.dagger:hilt-android-compiler:${Versions.HILT_VERSION}") testAnnotationProcessor("com.google.dagger:hilt-android-compiler:${Versions.HILT_VERSION}") diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt new file mode 100644 index 00000000..b4f33ef9 --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt @@ -0,0 +1,38 @@ +package co.nimblehq.compose.crypto.test + +import co.nimblehq.compose.crypto.util.DispatchersProvider +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@OptIn(ExperimentalCoroutinesApi::class) +class CoroutineTestRule : TestWatcher() { + + internal val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() + + override fun starting(description: Description?) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description?) { + super.finished(description) + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() + } + + val testDispatcherProvider = object : DispatchersProvider { + override val io: CoroutineDispatcher + get() = testDispatcher + override val main: CoroutineDispatcher + get() = testDispatcher + override val default: CoroutineDispatcher + get() = testDispatcher + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +fun CoroutineTestRule.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) { + testDispatcher.runBlockingTest(block) +} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt new file mode 100644 index 00000000..dafdca8d --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt @@ -0,0 +1,100 @@ +package co.nimblehq.compose.crypto.test + +import co.nimblehq.compose.crypto.domain.model.CoinDetail +import co.nimblehq.compose.crypto.domain.model.CoinItem +import java.math.BigDecimal + +object MockUtil { + + val myCoins = listOf( + CoinItem( + id = "bitcoin", + symbol = "btc", + coinName = "Bitcoin", + image = "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", + currentPrice = BigDecimal(21953), + marketCap = BigDecimal(418632879244), + marketCapRank = 1, + fullyDilutedValuation = BigDecimal(394474286491), + totalVolume = BigDecimal(40284988945), + high24h = BigDecimal(23014), + low24h = BigDecimal(21175), + priceChange24h = BigDecimal(777.55), + priceChangePercentage24h = 3.67201, + marketCapChange24h = BigDecimal(15300446085.0), + marketCapChangePercentage24h = 3.79351, + circulatingSupply = BigDecimal(19143668), + totalSupply = BigDecimal(21000000), + maxSupply = BigDecimal(21000000), + ath = BigDecimal(69045), + athChangePercentage = -68.93253, + athDate = "2021-11-10T14:24:19.604Z", + atl = BigDecimal(0.0398177), + atlChangePercentage = 661256.26362, + atlDate = "2017-10-19T00:00:00.000Z", + roi = CoinItem.RoiItem( + times = BigDecimal(106.82921216576392), + currency = "btc", + percentage = 10682.921216576393 + ), + lastUpdated = "2022-09-07T05:38:22.556Z", + priceChangePercentage24hInCurrency = 3.672009841642702 + ) + ) + + val trendingCoins = myCoins + + val coinDetail = CoinDetail( + id = "bitcoin", + symbol = "btc", + coinName = "Bitcoin", + image = CoinDetail.Image( + large = "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", + small = "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579", + thumb = "https://assets.coingecko.com/coins/images/1/thumb/bitcoin.png?1547033579" + ), + marketData = CoinDetail.MarketData( + currentPrice = mapOf("usd" to BigDecimal(19112.45)), + ath = mapOf("usd" to BigDecimal(69045)), + athChangePercentage = mapOf("usd" to -72.30426), + athDate = emptyMap(), + atl = mapOf("usd" to BigDecimal(67.81)), + atlChangePercentage = mapOf("usd" to 28100.4782), + atlDate = emptyMap(), + marketCap = mapOf("usd" to BigDecimal(366436890217)), + marketCapRank = 0, + fullyDilutedValuation = emptyMap(), + totalVolume = emptyMap(), + high24h = emptyMap(), + low24h = emptyMap(), + + priceChange24h = BigDecimal.ZERO, + priceChangePercentage24h = 0.0, + priceChangePercentage7d = 0.0, + priceChangePercentage14d = 0.0, + priceChangePercentage30d = 0.0, + priceChangePercentage60d = 0.0, + priceChangePercentage200d = 0.0, + priceChangePercentage1y = 0.0, + marketCapChange24h = BigDecimal.ZERO, + marketCapChangePercentage24h = 1.0166, + + priceChange24hInCurrency = emptyMap(), + priceChangePercentage24hInCurrency = mapOf("usd" to 0.74874), + priceChangePercentage7dInCurrency = emptyMap(), + priceChangePercentage14dInCurrency = emptyMap(), + priceChangePercentage30dInCurrency = emptyMap(), + priceChangePercentage60dInCurrency = emptyMap(), + priceChangePercentage200dInCurrency = emptyMap(), + priceChangePercentage1yInCurrency = emptyMap(), + marketCapChange24hInCurrency = emptyMap(), + marketCapChangePercentage24hInCurrency = emptyMap(), + + totalSupply = BigDecimal.ZERO, + maxSupply = BigDecimal.ZERO, + circulatingSupply = BigDecimal.ZERO, + + lastUpdated = "lastUpdated" + ) + ) +} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt new file mode 100644 index 00000000..238ca158 --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt @@ -0,0 +1,21 @@ +package co.nimblehq.compose.crypto.ui + +import co.nimblehq.compose.crypto.test.CoroutineTestRule +import co.nimblehq.compose.crypto.test.runBlockingTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope +import org.junit.Rule + +@ExperimentalCoroutinesApi +abstract class BaseScreenTest { + + @get:Rule + private var coroutineRule = CoroutineTestRule() + + protected fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = + coroutineRule.runBlockingTest(block) + + protected val testDispatcherProvider = coroutineRule.testDispatcherProvider + + protected val testDispatcher = coroutineRule.testDispatcher +} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt new file mode 100644 index 00000000..f5a569ac --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -0,0 +1,185 @@ +package co.nimblehq.compose.crypto.ui.screen + +import androidx.activity.compose.setContent +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import co.nimblehq.compose.crypto.test.MockUtil +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.domain.usecase.GetMyCoinsUseCase +import co.nimblehq.compose.crypto.domain.usecase.GetTrendingCoinsUseCase +import co.nimblehq.compose.crypto.extension.toFormattedString +import co.nimblehq.compose.crypto.ui.BaseScreenTest +import co.nimblehq.compose.crypto.ui.navigation.AppDestination +import co.nimblehq.compose.crypto.ui.screens.MainActivity +import co.nimblehq.compose.crypto.ui.screens.home.* +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class HomeScreenUITest : BaseScreenTest() { + + @get:Rule + val composeAndroidTestRule = createAndroidComposeRule() + + private val homeTitle: String + get() = composeAndroidTestRule.activity.getString(R.string.home_title) + + private val totalCoinsLabel: String + get() = composeAndroidTestRule.activity.getString(R.string.portfolio_card_total_coin_label) + + private val todayProfitLabel: String + get() = composeAndroidTestRule.activity.getString(R.string.portfolio_card_today_profit_label) + + private val errorGeneric: String + get() = composeAndroidTestRule.activity.getString(R.string.error_generic) + + private val expectedPriceChange: String + get() = composeAndroidTestRule.activity.getString( + R.string.coin_profit_percent, + MockUtil.trendingCoins.first().priceChangePercentage24hInCurrency.toFormattedString() + ) + + private val mockGetMyCoinsUseCase = mockk() + private val mockGetTrendingCoinsUseCase = mockk() + + private lateinit var viewModel: HomeViewModel + + private var appDestination: AppDestination? = null + + @Before + fun setUp() { + composeAndroidTestRule.activity.setContent { + HomeScreen( + viewModel = viewModel, + navigator = { destination -> appDestination = destination } + ) + } + + every { mockGetMyCoinsUseCase.execute(any()) } returns flowOf(MockUtil.myCoins) + every { mockGetTrendingCoinsUseCase.execute(any()) } returns flowOf(MockUtil.trendingCoins) + } + + @Test + fun when_enter_to_HomeScreen_it_render_the_PortfolioCard_properly() { + initViewModel() + + with(composeAndroidTestRule) { + onNodeWithTag(testTag = TestTagHomeTitle).assertTextEquals(homeTitle) + onNodeWithTag(testTag = TestTagTotalCoinsLabel).assertTextEquals(totalCoinsLabel) + onNodeWithTag(testTag = TestTagTodayCoinProfitLabel).assertTextEquals(todayProfitLabel) + onNodeWithTag(testTag = TestTagCardTotalCoins).assertTextEquals("$7,273,291") + onNodeWithTag(testTag = TestTagCardTodayProfit).assertTextEquals("$193,280") + } + } + + @Test + fun when_enter_to_HomeScreen_and_load_MyCoins_successfully_it_render_the_UI_properly() { + initViewModel() + + with(composeAndroidTestRule) { + with(MockUtil.myCoins.first()) { + onAllNodesWithTag( + testTag = TestTagCoinItemSymbol, + useUnmergedTree = true + ).onFirst().assertTextEquals(symbol.uppercase()) + + onAllNodesWithTag( + testTag = TestTagCoinItemCoinName, + useUnmergedTree = true + ).onFirst().assertTextEquals(coinName) + + onAllNodesWithTag( + testTag = TestTagCoinItemPriceChange, + useUnmergedTree = true + ).onFirst().onChild().assertTextEquals(expectedPriceChange) + } + } + } + + @Test + fun when_enter_to_HomeScreen_and_load_TrendingCoins_successfully_it_render_the_UI_properly() { + initViewModel() + + with(composeAndroidTestRule) { + with(MockUtil.trendingCoins.first()) { + onAllNodesWithTag( + testTag = TestTagTrendingItemSymbol, + useUnmergedTree = true + ).onFirst().assertTextEquals(symbol.uppercase()) + + onAllNodesWithTag( + testTag = TestTagTrendingItemCoinName, + useUnmergedTree = true + ).onFirst().assertTextEquals(coinName) + + onAllNodesWithTag( + testTag = TestTagTrendingItemPriceChange, + useUnmergedTree = true + ).onFirst().onChild().assertTextEquals(expectedPriceChange) + } + } + } + + @Test + fun when_clicked_on_MyCoin_item_it_navigates_to_DetailScreen() { + initViewModel() + + composeAndroidTestRule.onAllNodesWithTag(testTag = TestTagCoinItem).onFirst().performClick() + + appDestination shouldBe AppDestination.CoinDetail + } + + @Test + fun when_clicked_on_TrendingCoin_item_it_navigates_to_DetailScreen() { + initViewModel() + + composeAndroidTestRule.onAllNodesWithTag( + testTag = TestTagTrendingItem + ).onFirst().performClick() + + appDestination shouldBe AppDestination.CoinDetail + } + + @Test + fun when_enter_to_HomeScreen_and_load_MyCoins_failed_it_shows_the_Toast_properly() { + every { mockGetMyCoinsUseCase.execute(any()) } returns flow { + throw Throwable(errorGeneric) + } + + initViewModel() + + composeAndroidTestRule.onNodeWithTag( + testTag = TestTagCoinItem, + useUnmergedTree = true + ).assertDoesNotExist() + } + + @Test + fun when_enter_to_HomeScreen_and_load_TrendingCoins_failed_it_shows_the_Toast_properly() { + every { mockGetTrendingCoinsUseCase.execute(any()) } returns flow { + throw Throwable(errorGeneric) + } + + initViewModel() + + composeAndroidTestRule.onNodeWithTag( + testTag = TestTagTrendingItem, + useUnmergedTree = true + ).assertDoesNotExist() + } + + private fun initViewModel() { + viewModel = HomeViewModel( + dispatchers = testDispatcherProvider, + getMyCoinsUseCase = mockGetMyCoinsUseCase, + getTrendingCoinsUseCase = mockGetTrendingCoinsUseCase + ) + } +} diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt index a75e3322..e3ccfa2a 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt @@ -39,9 +39,9 @@ interface Output : BaseOutput { val trendingCoins: StateFlow> - val myCoinsError: SharedFlow + val myCoinsError: StateFlow - val trendingCoinsError: SharedFlow + val trendingCoinsError: StateFlow } @HiltViewModel diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index dd1367d6..f15a98dd 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -50,7 +50,7 @@ object Versions { const val TEST_JUNIT_ANDROIDX_EXT_VERSION = "1.1.3" const val TEST_JUNIT_VERSION = "4.13.2" const val TEST_KOTEST_VERSION = "4.6.3" - const val TEST_MOCKK_VERSION = "1.10.6" + const val TEST_MOCKK_VERSION = "1.12.3" const val TEST_ROBOLECTRIC_VERSION = "4.8.2" const val TEST_RUNNER_VERSION = "1.3.0" const val TEST_TURBINE_VERSION = "0.7.0" From 8c2bf0d99adfadbb742dec03467d944721264ca7 Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Fri, 17 Feb 2023 14:07:40 +0700 Subject: [PATCH 2/7] [#74] Remove BaseScreenTest and TestDispatchersProvider --- .../compose/crypto/test/CoroutineTestRule.kt | 38 ------------------- .../crypto/test/TestDispatchersProvider.kt | 21 ++++++++++ .../compose/crypto/ui/BaseScreenTest.kt | 21 ---------- .../crypto/ui/screen/HomeScreenUITest.kt | 8 ++-- 4 files changed, 24 insertions(+), 64 deletions(-) delete mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/test/TestDispatchersProvider.kt delete mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt deleted file mode 100644 index b4f33ef9..00000000 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/CoroutineTestRule.kt +++ /dev/null @@ -1,38 +0,0 @@ -package co.nimblehq.compose.crypto.test - -import co.nimblehq.compose.crypto.util.DispatchersProvider -import kotlinx.coroutines.* -import kotlinx.coroutines.test.* -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -@OptIn(ExperimentalCoroutinesApi::class) -class CoroutineTestRule : TestWatcher() { - - internal val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() - - override fun starting(description: Description?) { - super.starting(description) - Dispatchers.setMain(testDispatcher) - } - - override fun finished(description: Description?) { - super.finished(description) - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - } - - val testDispatcherProvider = object : DispatchersProvider { - override val io: CoroutineDispatcher - get() = testDispatcher - override val main: CoroutineDispatcher - get() = testDispatcher - override val default: CoroutineDispatcher - get() = testDispatcher - } -} - -@OptIn(ExperimentalCoroutinesApi::class) -fun CoroutineTestRule.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) { - testDispatcher.runBlockingTest(block) -} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/TestDispatchersProvider.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/TestDispatchersProvider.kt new file mode 100644 index 00000000..6d9d144a --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/TestDispatchersProvider.kt @@ -0,0 +1,21 @@ +package co.nimblehq.compose.crypto.test + +import co.nimblehq.compose.crypto.util.DispatchersProvider +import kotlinx.coroutines.* +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher + +@OptIn(ExperimentalCoroutinesApi::class) +object TestDispatchersProvider : DispatchersProvider { + + private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher() + + override val io: CoroutineDispatcher + get() = testDispatcher + + override val main: CoroutineDispatcher + get() = testDispatcher + + override val default: CoroutineDispatcher + get() = testDispatcher +} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt deleted file mode 100644 index 238ca158..00000000 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/BaseScreenTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package co.nimblehq.compose.crypto.ui - -import co.nimblehq.compose.crypto.test.CoroutineTestRule -import co.nimblehq.compose.crypto.test.runBlockingTest -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScope -import org.junit.Rule - -@ExperimentalCoroutinesApi -abstract class BaseScreenTest { - - @get:Rule - private var coroutineRule = CoroutineTestRule() - - protected fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = - coroutineRule.runBlockingTest(block) - - protected val testDispatcherProvider = coroutineRule.testDispatcherProvider - - protected val testDispatcher = coroutineRule.testDispatcher -} diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt index f5a569ac..303ae488 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -8,22 +8,20 @@ import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.domain.usecase.GetMyCoinsUseCase import co.nimblehq.compose.crypto.domain.usecase.GetTrendingCoinsUseCase import co.nimblehq.compose.crypto.extension.toFormattedString -import co.nimblehq.compose.crypto.ui.BaseScreenTest +import co.nimblehq.compose.crypto.test.TestDispatchersProvider import co.nimblehq.compose.crypto.ui.navigation.AppDestination import co.nimblehq.compose.crypto.ui.screens.MainActivity import co.nimblehq.compose.crypto.ui.screens.home.* import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import org.junit.Before import org.junit.Rule import org.junit.Test -@ExperimentalCoroutinesApi -class HomeScreenUITest : BaseScreenTest() { +class HomeScreenUITest { @get:Rule val composeAndroidTestRule = createAndroidComposeRule() @@ -177,7 +175,7 @@ class HomeScreenUITest : BaseScreenTest() { private fun initViewModel() { viewModel = HomeViewModel( - dispatchers = testDispatcherProvider, + dispatchers = TestDispatchersProvider, getMyCoinsUseCase = mockGetMyCoinsUseCase, getTrendingCoinsUseCase = mockGetTrendingCoinsUseCase ) From ccdf8649722015c08d0e24b91dadbf69c4e0fbb5 Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Fri, 17 Feb 2023 14:59:28 +0700 Subject: [PATCH 3/7] [#74] Add test cases for LoadingProgress --- .../crypto/ui/screen/HomeScreenUITest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt index 303ae488..93a9bad2 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -15,6 +15,7 @@ import co.nimblehq.compose.crypto.ui.screens.home.* import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import org.junit.Before @@ -77,6 +78,24 @@ class HomeScreenUITest { } } + @Test + fun when_loading_MyCoins_it_renders_the_LoadingProgress_properly() { + every { mockGetMyCoinsUseCase.execute(any()) } returns flow { delay(500) } + + initViewModel() + + composeAndroidTestRule.onNodeWithTag(testTag = TestTagCoinsLoader).assertIsDisplayed() + } + + @Test + fun when_loading_TrendingCoins_it_renders_the_LoadingProgress_properly() { + every { mockGetTrendingCoinsUseCase.execute(any()) } returns flow { delay(500) } + + initViewModel() + + composeAndroidTestRule.onNodeWithTag(testTag = TestTagCoinsLoader).assertIsDisplayed() + } + @Test fun when_enter_to_HomeScreen_and_load_MyCoins_successfully_it_render_the_UI_properly() { initViewModel() From b905c32062c2214c4d6727b3b610468ca75df397 Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Fri, 17 Feb 2023 15:46:30 +0700 Subject: [PATCH 4/7] [#74] Rename Test Cases --- .../crypto/ui/screen/HomeScreenUITest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt index 93a9bad2..5b17f315 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -66,7 +66,7 @@ class HomeScreenUITest { } @Test - fun when_enter_to_HomeScreen_it_render_the_PortfolioCard_properly() { + fun hen_entering_HomeScreen__it_renders_the_PortfolioCard_properly() { initViewModel() with(composeAndroidTestRule) { @@ -79,7 +79,7 @@ class HomeScreenUITest { } @Test - fun when_loading_MyCoins_it_renders_the_LoadingProgress_properly() { + fun when_loading_MyCoins__it_renders_the_LoadingProgress_properly() { every { mockGetMyCoinsUseCase.execute(any()) } returns flow { delay(500) } initViewModel() @@ -88,7 +88,7 @@ class HomeScreenUITest { } @Test - fun when_loading_TrendingCoins_it_renders_the_LoadingProgress_properly() { + fun when_loading_TrendingCoins__it_renders_the_LoadingProgress_properly() { every { mockGetTrendingCoinsUseCase.execute(any()) } returns flow { delay(500) } initViewModel() @@ -97,7 +97,7 @@ class HomeScreenUITest { } @Test - fun when_enter_to_HomeScreen_and_load_MyCoins_successfully_it_render_the_UI_properly() { + fun when_entering_HomeScreen_and_loading_MyCoins_successfully__it_renders_the_UI_properly() { initViewModel() with(composeAndroidTestRule) { @@ -121,7 +121,7 @@ class HomeScreenUITest { } @Test - fun when_enter_to_HomeScreen_and_load_TrendingCoins_successfully_it_render_the_UI_properly() { + fun when_entering_to_the_HomeScreen_and_loading_TrendingCoins_successfully__it_renders_the_UI_properly() { initViewModel() with(composeAndroidTestRule) { @@ -145,7 +145,7 @@ class HomeScreenUITest { } @Test - fun when_clicked_on_MyCoin_item_it_navigates_to_DetailScreen() { + fun when_clicked_on_MyCoin_item__it_navigates_to_DetailScreen() { initViewModel() composeAndroidTestRule.onAllNodesWithTag(testTag = TestTagCoinItem).onFirst().performClick() @@ -154,7 +154,7 @@ class HomeScreenUITest { } @Test - fun when_clicked_on_TrendingCoin_item_it_navigates_to_DetailScreen() { + fun when_clicked_on_TrendingCoin_item__it_navigates_to_DetailScreen() { initViewModel() composeAndroidTestRule.onAllNodesWithTag( @@ -165,7 +165,7 @@ class HomeScreenUITest { } @Test - fun when_enter_to_HomeScreen_and_load_MyCoins_failed_it_shows_the_Toast_properly() { + fun when_entering_to_the_HomeScreen_and_loading_MyCoins_failed__it_shows_the_error_message() { every { mockGetMyCoinsUseCase.execute(any()) } returns flow { throw Throwable(errorGeneric) } @@ -179,7 +179,7 @@ class HomeScreenUITest { } @Test - fun when_enter_to_HomeScreen_and_load_TrendingCoins_failed_it_shows_the_Toast_properly() { + fun when_entering_to_the_HomeScreen_and_loading_TrendingCoins_failed__it_shows_the_error_message() { every { mockGetTrendingCoinsUseCase.execute(any()) } returns flow { throw Throwable(errorGeneric) } From ad4b27fc7100ef98348b8ca9fee48733b9853da7 Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Mon, 20 Feb 2023 11:10:23 +0700 Subject: [PATCH 5/7] [#74] Leave TODO for add the assertion error message --- .../nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt index 5b17f315..40cd2218 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -66,7 +66,7 @@ class HomeScreenUITest { } @Test - fun hen_entering_HomeScreen__it_renders_the_PortfolioCard_properly() { + fun when_entering_HomeScreen__it_renders_the_PortfolioCard_properly() { initViewModel() with(composeAndroidTestRule) { @@ -176,6 +176,8 @@ class HomeScreenUITest { testTag = TestTagCoinItem, useUnmergedTree = true ).assertDoesNotExist() + + // TODO: Add the assertion for the error message } @Test @@ -190,6 +192,8 @@ class HomeScreenUITest { testTag = TestTagTrendingItem, useUnmergedTree = true ).assertDoesNotExist() + + // TODO: Add the assertion for the error message } private fun initViewModel() { From 6ab1ce977dce25cd32846e3bb8fecf09833fd5ba Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Mon, 20 Feb 2023 11:24:53 +0700 Subject: [PATCH 6/7] [#74] Remove Test tag for text --- .../crypto/ui/screen/HomeScreenUITest.kt | 44 +++-------- .../crypto/ui/screens/home/CoinItem.kt | 16 +--- .../crypto/ui/screens/home/HomeScreen.kt | 4 +- .../crypto/ui/screens/home/PortfolioCard.kt | 18 +---- .../crypto/ui/screens/home/TrendingItem.kt | 12 +-- .../crypto/ui/screens/home/HomeScreenTest.kt | 78 ++++--------------- 6 files changed, 38 insertions(+), 134 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt index 40cd2218..5db5bb08 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt @@ -70,11 +70,11 @@ class HomeScreenUITest { initViewModel() with(composeAndroidTestRule) { - onNodeWithTag(testTag = TestTagHomeTitle).assertTextEquals(homeTitle) - onNodeWithTag(testTag = TestTagTotalCoinsLabel).assertTextEquals(totalCoinsLabel) - onNodeWithTag(testTag = TestTagTodayCoinProfitLabel).assertTextEquals(todayProfitLabel) - onNodeWithTag(testTag = TestTagCardTotalCoins).assertTextEquals("$7,273,291") - onNodeWithTag(testTag = TestTagCardTodayProfit).assertTextEquals("$193,280") + onNodeWithText(homeTitle).assertIsDisplayed() + onNodeWithText(totalCoinsLabel).assertIsDisplayed() + onNodeWithText(todayProfitLabel).assertIsDisplayed() + onNodeWithText("$7,273,291").assertIsDisplayed() + onNodeWithText("$193,280").assertIsDisplayed() } } @@ -102,20 +102,9 @@ class HomeScreenUITest { with(composeAndroidTestRule) { with(MockUtil.myCoins.first()) { - onAllNodesWithTag( - testTag = TestTagCoinItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagCoinItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagCoinItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } } } @@ -126,20 +115,9 @@ class HomeScreenUITest { with(composeAndroidTestRule) { with(MockUtil.trendingCoins.first()) { - onAllNodesWithTag( - testTag = TestTagTrendingItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagTrendingItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagTrendingItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt index 13d755ea..4fed6022 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/CoinItem.kt @@ -9,7 +9,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -23,11 +22,6 @@ import co.nimblehq.compose.crypto.ui.theme.* import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import coil.compose.rememberAsyncImagePainter -const val TestTagCoinItemSymbol = "CoinItemCoinSymbol" -const val TestTagCoinItemCoinName = "CoinItemCoinName" -const val TestTagCoinItemPrice = "CoinItemPrice" -const val TestTagCoinItemPriceChange = "CoinItemPriceChange" - @Composable fun CoinItem( modifier: Modifier = Modifier, @@ -67,8 +61,7 @@ fun CoinItem( .constrainAs(coinSymbol) { top.linkTo(parent.top) start.linkTo(anchor = logo.end, margin = Dp16) - } - .testTag(tag = TestTagCoinItemSymbol), + }, text = coinItem.symbol.uppercase(), color = AppTheme.colors.text, style = AppTheme.styles.semiBold16 @@ -81,8 +74,7 @@ fun CoinItem( start.linkTo(coinSymbol.start) top.linkTo(coinSymbol.bottom) width = Dimension.preferredWrapContent - } - .testTag(tag = TestTagCoinItemCoinName), + }, text = coinItem.coinName, color = AppTheme.colors.coinNameText, style = AppTheme.styles.medium14 @@ -94,8 +86,7 @@ fun CoinItem( start.linkTo(logo.start) top.linkTo(anchor = coinName.bottom, margin = Dp14) width = Dimension.preferredWrapContent - } - .testTag(tag = TestTagCoinItemPrice), + }, text = stringResource( R.string.coin_currency, coinItem.currentPrice.toFormattedString() @@ -113,7 +104,6 @@ fun CoinItem( bottom.linkTo(parent.bottom) width = Dimension.preferredWrapContent } - .testTag(tag = TestTagCoinItemPriceChange) ) } } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt index 052b1cea..12f39286 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt @@ -33,7 +33,6 @@ import timber.log.Timber private const val LIST_ITEM_LOAD_MORE_THRESHOLD = 0 -const val TestTagHomeTitle = "HomeTitle" const val TestTagTrendingItem = "TrendingItem" const val TestTagCoinItem = "CoinItem" const val TestTagCoinsLoader = "CoinsLoader" @@ -120,8 +119,7 @@ private fun HomeScreenContent( Text( modifier = Modifier .fillMaxWidth() - .padding(top = Dp16) - .testTag(TestTagHomeTitle), + .padding(top = Dp16), text = stringResource(id = R.string.home_title), textAlign = TextAlign.Center, style = AppTheme.styles.semiBold24, diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt index 567d43b0..69231b74 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/PortfolioCard.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout @@ -17,11 +16,6 @@ import co.nimblehq.compose.crypto.R import co.nimblehq.compose.crypto.ui.common.price.PriceChangeButton import co.nimblehq.compose.crypto.ui.theme.* -const val TestTagTotalCoinsLabel = "CardTotalCoinsLabel" -const val TestTagTodayCoinProfitLabel = "todayProfitLabel" -const val TestTagCardTotalCoins = "CardTotalCoins" -const val TestTagCardTodayProfit = "CardTodayProfit" - @Composable fun PortfolioCard( modifier: Modifier @@ -49,8 +43,7 @@ fun PortfolioCard( modifier = Modifier .constrainAs(totalCoinsLabel) { start.linkTo(parent.start) - } - .testTag(TestTagTotalCoinsLabel), + }, text = stringResource(R.string.portfolio_card_total_coin_label), style = AppTheme.styles.lightSilverMedium16 ) @@ -59,8 +52,7 @@ fun PortfolioCard( modifier = Modifier .constrainAs(totalCoins) { top.linkTo(totalCoinsLabel.bottom, margin = Dp8) - } - .testTag(tag = TestTagCardTotalCoins), + }, // TODO: Remove dummy value when work on Integrate. text = stringResource(R.string.coin_currency, "7,273,291"), style = AppTheme.styles.whiteSemiBold24 @@ -70,8 +62,7 @@ fun PortfolioCard( modifier = Modifier .constrainAs(todayProfitLabel) { top.linkTo(totalCoins.bottom, margin = Dp40) - } - .testTag(tag = TestTagTodayCoinProfitLabel), + }, text = stringResource(R.string.portfolio_card_today_profit_label), style = AppTheme.styles.lightSilverMedium16 ) @@ -80,8 +71,7 @@ fun PortfolioCard( modifier = Modifier .constrainAs(todayProfit) { top.linkTo(todayProfitLabel.bottom, margin = Dp8) - } - .testTag(tag = TestTagCardTodayProfit), + }, // TODO: Remove dummy value when work on Integrate. text = stringResource(R.string.coin_currency, "193,280"), style = AppTheme.styles.whiteSemiBold24 diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt index b619df14..f49e7444 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/TrendingItem.kt @@ -9,7 +9,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.constraintlayout.compose.ConstraintLayout @@ -20,10 +19,6 @@ import co.nimblehq.compose.crypto.ui.theme.* import co.nimblehq.compose.crypto.ui.uimodel.CoinItemUiModel import coil.compose.rememberAsyncImagePainter -const val TestTagTrendingItemSymbol = "TrendingItemSymbol" -const val TestTagTrendingItemCoinName = "TrendingItemCoinName" -const val TestTagTrendingItemPriceChange = "TrendingItemPriceChange" - @Suppress("LongMethod") @Composable fun TrendingItem( @@ -69,8 +64,7 @@ fun TrendingItem( top.linkTo(parent.top) bottom.linkTo(coinName.top) start.linkTo(anchor = logo.end, margin = Dp16) - } - .testTag(tag = TestTagTrendingItemSymbol), + }, text = coinItem.symbol.uppercase(), color = AppTheme.colors.text, style = AppTheme.styles.semiBold16 @@ -83,8 +77,7 @@ fun TrendingItem( top.linkTo(coinSymbol.bottom) bottom.linkTo(parent.bottom) width = Dimension.preferredWrapContent - } - .testTag(tag = TestTagTrendingItemCoinName), + }, text = coinItem.coinName, color = AppTheme.colors.coinNameText, style = AppTheme.styles.medium14 @@ -99,7 +92,6 @@ fun TrendingItem( bottom.linkTo(coinName.bottom) width = Dimension.preferredWrapContent } - .testTag(tag = TestTagTrendingItemPriceChange) ) } } diff --git a/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreenTest.kt b/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreenTest.kt index 298cceaf..4c08e397 100644 --- a/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreenTest.kt +++ b/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreenTest.kt @@ -74,11 +74,11 @@ class HomeScreenTest : BaseViewModelTest() { initViewModel() with(composeAndroidTestRule) { - onNodeWithTag(testTag = TestTagHomeTitle).assertTextEquals(homeTitle) - onNodeWithTag(testTag = TestTagTotalCoinsLabel).assertTextEquals(totalCoinsLabel) - onNodeWithTag(testTag = TestTagTodayCoinProfitLabel).assertTextEquals(todayProfitLabel) - onNodeWithTag(testTag = TestTagCardTotalCoins).assertTextEquals("$7,273,291") - onNodeWithTag(testTag = TestTagCardTodayProfit).assertTextEquals("$193,280") + onNodeWithText(homeTitle).assertIsDisplayed() + onNodeWithText(totalCoinsLabel).assertIsDisplayed() + onNodeWithText(todayProfitLabel).assertIsDisplayed() + onNodeWithText("$7,273,291").assertIsDisplayed() + onNodeWithText("$193,280").assertIsDisplayed() } } @@ -92,20 +92,9 @@ class HomeScreenTest : BaseViewModelTest() { onNodeWithTag(testTag = TestTagCoinsLoader).assertIsDisplayed() with(MockUtil.myCoins.first()) { - onAllNodesWithTag( - testTag = TestTagCoinItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagCoinItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagCoinItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } } } @@ -120,20 +109,9 @@ class HomeScreenTest : BaseViewModelTest() { onNodeWithTag(testTag = TestTagCoinsLoader).assertIsDisplayed() with(MockUtil.trendingCoins.first()) { - onAllNodesWithTag( - testTag = TestTagTrendingItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagTrendingItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagTrendingItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } } } @@ -202,20 +180,9 @@ class HomeScreenTest : BaseViewModelTest() { with(composeAndroidTestRule) { with(MockUtil.myCoins.first()) { - onAllNodesWithTag( - testTag = TestTagCoinItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagCoinItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagCoinItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } onRoot().performTouchInput { swipeDown() } @@ -234,20 +201,9 @@ class HomeScreenTest : BaseViewModelTest() { onNodeWithTag(testTag = TestTagCoinsLoader).assertIsDisplayed() with(MockUtil.trendingCoins.first()) { - onAllNodesWithTag( - testTag = TestTagTrendingItemSymbol, - useUnmergedTree = true - ).onFirst().assertTextEquals(symbol.uppercase()) - - onAllNodesWithTag( - testTag = TestTagTrendingItemCoinName, - useUnmergedTree = true - ).onFirst().assertTextEquals(coinName) - - onAllNodesWithTag( - testTag = TestTagTrendingItemPriceChange, - useUnmergedTree = true - ).onFirst().onChild().assertTextEquals(expectedPriceChange) + onAllNodesWithText(symbol.uppercase()).onFirst().assertIsDisplayed() + onAllNodesWithText(coinName).onFirst().assertIsDisplayed() + onAllNodesWithText(expectedPriceChange).onFirst().assertIsDisplayed() } onRoot().performTouchInput { swipeDown() } From 127a7e0db2e22ffd1727062fd5629bb2ecd6f2de Mon Sep 17 00:00:00 2001 From: Wadeewee Date: Mon, 20 Feb 2023 12:52:53 +0700 Subject: [PATCH 7/7] [#74] Add methods for reset error to null --- .../crypto/ui/screens/home/HomeScreen.kt | 4 ++++ .../crypto/ui/screens/home/HomeViewModel.kt | 16 +++++++++++++++ .../ui/screens/home/HomeViewModelTest.kt | 20 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt index 12f39286..36b02841 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeScreen.kt @@ -65,10 +65,14 @@ fun HomeScreen( myCoinsError?.let { error -> Toast.makeText(context, error.userReadableMessage(context), Toast.LENGTH_SHORT).show() + + viewModel.input.clearMyCoinsError() } trendingCoinsError?.let { error -> Toast.makeText(context, error.userReadableMessage(context), Toast.LENGTH_SHORT).show() + + viewModel.input.clearTrendingCoinsError() } HomeScreenContent( diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt index e3ccfa2a..14d38678 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModel.kt @@ -27,6 +27,10 @@ interface Input : BaseInput { fun onMyCoinsItemClick(coin: CoinItemUiModel) fun onTrendingCoinsItemClick(coin: CoinItemUiModel) + + fun clearMyCoinsError() + + fun clearTrendingCoinsError() } interface Output : BaseOutput { @@ -157,4 +161,16 @@ class HomeViewModel @Inject constructor( _navigator.emit(AppDestination.CoinDetail.buildDestination(coin.id)) } } + + override fun clearMyCoinsError() { + execute { + _myCoinsError.emit(null) + } + } + + override fun clearTrendingCoinsError() { + execute { + _trendingCoinsError.emit(null) + } + } } diff --git a/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModelTest.kt b/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModelTest.kt index dd3a3d1c..4fcc9adf 100644 --- a/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModelTest.kt +++ b/app/src/test/java/co/nimblehq/compose/crypto/ui/screens/home/HomeViewModelTest.kt @@ -119,6 +119,26 @@ class HomeViewModelTest : BaseViewModelTest() { } } + @Test + fun `When calling clearMyCoinsError, it should reset myCoinsError to null`() = + runBlockingTest { + initViewModel() + viewModel.output.myCoinsError.test { + viewModel.input.clearMyCoinsError() + expectMostRecentItem() shouldBe null + } + } + + @Test + fun `When calling clearTrendingCoinsError, it should reset trendingCoinsError to null`() = + runBlockingTest { + initViewModel() + viewModel.output.trendingCoinsError.test { + viewModel.input.clearTrendingCoinsError() + expectMostRecentItem() shouldBe null + } + } + private fun initViewModel() { viewModel = HomeViewModel( testDispatcherProvider,