From 447cd7eba311c9cb4b4f302bf632fe8bd4e9784c Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Thu, 12 Sep 2024 15:32:17 -0700 Subject: [PATCH] Convert InterestsListDetailScreenTest to unit test (#1560) * Convert InterestsListDetailScreenTest to Robolectric Change-Id: I751f6ccc8bf16465fb6a9effb8a5d738a184d778 * Fix import alias Change-Id: I3726858384bfe842eb717bae72c309284c524f06 * Add waitForIdle Change-Id: I702fbca6ba79e3705e3226b0f3088923c89fc2cb --- app/build.gradle.kts | 1 + .../ui}/InterestsListDetailScreenTest.kt | 116 ++++++++---------- gradle/libs.versions.toml | 1 + 3 files changed, 51 insertions(+), 67 deletions(-) rename app/src/{androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane => testDemo/kotlin/com/google/samples/apps/nowinandroid/ui}/InterestsListDetailScreenTest.kt (63%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b577a5bc..1abc44bac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,6 +116,7 @@ dependencies { testImplementation(projects.core.dataTest) testImplementation(libs.hilt.android.testing) testImplementation(projects.sync.syncTest) + testImplementation(libs.kotlin.test) testDemoImplementation(libs.robolectric) testDemoImplementation(libs.roborazzi) diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreenTest.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/InterestsListDetailScreenTest.kt similarity index 63% rename from app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreenTest.kt rename to app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/InterestsListDetailScreenTest.kt index 21ac3e920..a5b243537 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreenTest.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/InterestsListDetailScreenTest.kt @@ -14,43 +14,49 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.ui.interests2pane +package com.google.samples.apps.nowinandroid.ui import androidx.activity.compose.BackHandler -import androidx.compose.material3.adaptive.Posture -import androidx.compose.material3.adaptive.WindowAdaptiveInfo -import androidx.compose.ui.test.DeviceConfigurationOverride -import androidx.compose.ui.test.ForcedSize +import androidx.annotation.StringRes import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp import androidx.test.espresso.Espresso -import androidx.window.core.layout.WindowSizeClass import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.Topic -import com.google.samples.apps.nowinandroid.ui.stringResource +import com.google.samples.apps.nowinandroid.ui.interests2pane.InterestsListDetailScreen import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config import javax.inject.Inject +import kotlin.properties.ReadOnlyProperty import kotlin.test.assertTrue import com.google.samples.apps.nowinandroid.feature.topic.R as FeatureTopicR +private const val EXPANDED_WIDTH = "w1200dp-h840dp" +private const val COMPACT_WIDTH = "w412dp-h915dp" + @HiltAndroidTest +@RunWith(RobolectricTestRunner::class) +@Config(application = HiltTestApplication::class) class InterestsListDetailScreenTest { + @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @@ -64,6 +70,11 @@ class InterestsListDetailScreenTest { @Inject lateinit var topicsRepository: TopicsRepository + /** Convenience function for getting all topics during tests, */ + private fun getTopics(): List = runBlocking { + topicsRepository.getTopics().first().sortedBy { it.name } + } + // The strings used for matching in these tests. private val placeholderText by composeTestRule.stringResource(FeatureTopicR.string.feature_topic_select_an_interest) private val listPaneTag = "interests:topics" @@ -71,39 +82,18 @@ class InterestsListDetailScreenTest { private val Topic.testTag get() = "topic:${this.id}" - // Overrides for device sizes. - private enum class TestDeviceConfig(widthDp: Float, heightDp: Float) { - Compact(412f, 915f), - Expanded(1200f, 840f), - ; - - val sizeOverride = DeviceConfigurationOverride.ForcedSize(DpSize(widthDp.dp, heightDp.dp)) - val adaptiveInfo = WindowAdaptiveInfo( - windowSizeClass = WindowSizeClass.compute(widthDp, heightDp), - windowPosture = Posture(), - ) - } - @Before fun setup() { hiltRule.inject() } - /** Convenience function for getting all topics during tests, */ - private fun getTopics(): List = runBlocking { - topicsRepository.getTopics().first() - } - @Test + @Config(qualifiers = EXPANDED_WIDTH) fun expandedWidth_initialState_showsTwoPanesWithPlaceholder() { composeTestRule.apply { setContent { - with(TestDeviceConfig.Expanded) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } - } + NiaTheme { + InterestsListDetailScreen() } } @@ -113,15 +103,12 @@ class InterestsListDetailScreenTest { } @Test + @Config(qualifiers = COMPACT_WIDTH) fun compactWidth_initialState_showsListPane() { composeTestRule.apply { setContent { - with(TestDeviceConfig.Compact) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } - } + NiaTheme { + InterestsListDetailScreen() } } @@ -131,15 +118,12 @@ class InterestsListDetailScreenTest { } @Test + @Config(qualifiers = EXPANDED_WIDTH) fun expandedWidth_topicSelected_updatesDetailPane() { composeTestRule.apply { setContent { - with(TestDeviceConfig.Expanded) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } - } + NiaTheme { + InterestsListDetailScreen() } } @@ -153,15 +137,12 @@ class InterestsListDetailScreenTest { } @Test + @Config(qualifiers = COMPACT_WIDTH) fun compactWidth_topicSelected_showsTopicDetailPane() { composeTestRule.apply { setContent { - with(TestDeviceConfig.Compact) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } - } + NiaTheme { + InterestsListDetailScreen() } } @@ -175,27 +156,25 @@ class InterestsListDetailScreenTest { } @Test + @Config(qualifiers = EXPANDED_WIDTH) fun expandedWidth_backPressFromTopicDetail_leavesInterests() { var unhandledBackPress = false composeTestRule.apply { setContent { - with(TestDeviceConfig.Expanded) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - // Back press should not be handled by the two pane layout, and thus - // "fall through" to this BackHandler. - BackHandler { - unhandledBackPress = true - } - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } + NiaTheme { + // Back press should not be handled by the two pane layout, and thus + // "fall through" to this BackHandler. + BackHandler { + unhandledBackPress = true } + InterestsListDetailScreen() } } val firstTopic = getTopics().first() onNodeWithText(firstTopic.name).performClick() + waitForIdle() Espresso.pressBack() assertTrue(unhandledBackPress) @@ -203,21 +182,19 @@ class InterestsListDetailScreenTest { } @Test + @Config(qualifiers = COMPACT_WIDTH) fun compactWidth_backPressFromTopicDetail_showsListPane() { composeTestRule.apply { setContent { - with(TestDeviceConfig.Compact) { - DeviceConfigurationOverride(override = sizeOverride) { - NiaTheme { - InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo) - } - } + NiaTheme { + InterestsListDetailScreen() } } val firstTopic = getTopics().first() onNodeWithText(firstTopic.name).performClick() + waitForIdle() Espresso.pressBack() onNodeWithTag(listPaneTag).assertIsDisplayed() @@ -226,3 +203,8 @@ class InterestsListDetailScreenTest { } } } + +private fun AndroidComposeTestRule<*, *>.stringResource( + @StringRes resId: Int, +): ReadOnlyProperty = + ReadOnlyProperty { _, _ -> activity.getString(resId) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00c180194..ba9c36bfd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -125,6 +125,7 @@ hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.r hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" } javax-inject = { module = "javax.inject:javax.inject", version = "1" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" }