From d27ee63d2533881ba606f0f35fe5e00ab05fb49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Alc=C3=A9rreca?= Date: Fri, 13 Sep 2024 16:46:10 +0000 Subject: [PATCH 1/2] Removes sharedTest trick and Robolectric --- JetNews/app/build.gradle.kts | 24 ------ .../com/example/jetnews/HomeScreenTests.kt | 83 ------------------- .../java/com/example/jetnews/JetnewsTests.kt | 75 ----------------- .../com/example/jetnews/TestAppContainer.kt | 35 -------- .../java/com/example/jetnews/TestHelper.kt | 34 -------- .../src/test/resources/robolectric.properties | 8 -- 6 files changed, 259 deletions(-) delete mode 100644 JetNews/app/src/sharedTest/java/com/example/jetnews/HomeScreenTests.kt delete mode 100644 JetNews/app/src/sharedTest/java/com/example/jetnews/JetnewsTests.kt delete mode 100644 JetNews/app/src/sharedTest/java/com/example/jetnews/TestAppContainer.kt delete mode 100644 JetNews/app/src/sharedTest/java/com/example/jetnews/TestHelper.kt delete mode 100644 JetNews/app/src/test/resources/robolectric.properties diff --git a/JetNews/app/build.gradle.kts b/JetNews/app/build.gradle.kts index f0cad0c93b..b827bd22b4 100644 --- a/JetNews/app/build.gradle.kts +++ b/JetNews/app/build.gradle.kts @@ -60,24 +60,6 @@ android { } } - testOptions { - unitTests { - isReturnDefaultValues = true - isIncludeAndroidResources = true - } - } - - // Tests can be Robolectric or instrumented tests - sourceSets { - val sharedTestDir = "src/sharedTest/java" - getByName("test") { - java.srcDir(sharedTestDir) - } - getByName("androidTest") { - java.srcDir(sharedTestDir) - } - } - compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -146,11 +128,5 @@ dependencies { androidTestImplementation(libs.kotlinx.coroutines.test) androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.androidx.compose.ui.test.junit4) - // Robolectric dependencies - testImplementation(libs.androidx.compose.ui.test.junit4) - testImplementation(libs.robolectric) } -tasks.withType().configureEach { - systemProperties.put("robolectric.logging", "stdout") -} diff --git a/JetNews/app/src/sharedTest/java/com/example/jetnews/HomeScreenTests.kt b/JetNews/app/src/sharedTest/java/com/example/jetnews/HomeScreenTests.kt deleted file mode 100644 index 5d1432ad0d..0000000000 --- a/JetNews/app/src/sharedTest/java/com/example/jetnews/HomeScreenTests.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetnews - -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.example.jetnews.ui.home.HomeFeedScreen -import com.example.jetnews.ui.home.HomeUiState -import com.example.jetnews.ui.theme.JetnewsTheme -import com.example.jetnews.utils.ErrorMessage -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class HomeScreenTests { - - @get:Rule - val composeTestRule = createComposeRule() - - /** - * Checks that the Snackbar is shown when the HomeScreen data contains an error. - */ - @Test - fun postsContainError_snackbarShown() { - val snackbarHostState = SnackbarHostState() - composeTestRule.setContent { - JetnewsTheme { - - // When the Home screen receives data with an error - HomeFeedScreen( - uiState = HomeUiState.NoPosts( - isLoading = false, - errorMessages = listOf(ErrorMessage(0L, R.string.load_error)), - searchInput = "" - ), - showTopAppBar = false, - onToggleFavorite = {}, - onSelectPost = {}, - onRefreshPosts = {}, - onErrorDismiss = {}, - openDrawer = {}, - homeListLazyListState = rememberLazyListState(), - snackbarHostState = snackbarHostState, - onSearchInputChanged = {} - ) - } - } - - // Then the first message received in the Snackbar is an error message - runBlocking { - // snapshotFlow converts a State to a Kotlin Flow so we can observe it - // wait for the first a non-null `currentSnackbarData` - val actualSnackbarText = snapshotFlow { snackbarHostState.currentSnackbarData } - .filterNotNull().first().visuals.message - val expectedSnackbarText = InstrumentationRegistry.getInstrumentation() - .targetContext.resources.getString(R.string.load_error) - assertEquals(expectedSnackbarText, actualSnackbarText) - } - } -} diff --git a/JetNews/app/src/sharedTest/java/com/example/jetnews/JetnewsTests.kt b/JetNews/app/src/sharedTest/java/com/example/jetnews/JetnewsTests.kt deleted file mode 100644 index ed48a5ecc3..0000000000 --- a/JetNews/app/src/sharedTest/java/com/example/jetnews/JetnewsTests.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetnews - -import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.onRoot -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.printToString -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class JetnewsTests { - - @get:Rule - val composeTestRule = createComposeRule() - - @Before - fun setUp() { - // Using targetContext as the Context of the instrumentation code - composeTestRule.launchJetNewsApp(ApplicationProvider.getApplicationContext()) - } - - @Test - fun app_launches() { - composeTestRule.onNodeWithText("Top stories for you").assertExists() - } - - @Test - fun app_opensArticle() { - - println(composeTestRule.onRoot().printToString()) - composeTestRule.onAllNodes(hasText("Manuel Vivo", substring = true))[0].performClick() - - println(composeTestRule.onRoot().printToString()) - try { - composeTestRule.onAllNodes(hasText("3 min read", substring = true))[0].assertExists() - } catch (e: AssertionError) { - println(composeTestRule.onRoot().printToString()) - throw e - } - } - - @Test - fun app_opensInterests() { - composeTestRule.onNodeWithContentDescription( - label = "Open navigation drawer", - useUnmergedTree = true - ).performClick() - composeTestRule.onNodeWithText("Interests").performClick() - // TODO - this fails on CI but not locally. (https://github.com/android/compose-samples/issues/1442) - // composeTestRule.waitUntilAtLeastOneExists(hasText("Topics"), 5000L) - } -} diff --git a/JetNews/app/src/sharedTest/java/com/example/jetnews/TestAppContainer.kt b/JetNews/app/src/sharedTest/java/com/example/jetnews/TestAppContainer.kt deleted file mode 100644 index 2b8ed36c6c..0000000000 --- a/JetNews/app/src/sharedTest/java/com/example/jetnews/TestAppContainer.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetnews - -import android.content.Context -import com.example.jetnews.data.AppContainer -import com.example.jetnews.data.interests.InterestsRepository -import com.example.jetnews.data.interests.impl.FakeInterestsRepository -import com.example.jetnews.data.posts.PostsRepository -import com.example.jetnews.data.posts.impl.BlockingFakePostsRepository - -class TestAppContainer(private val context: Context) : AppContainer { - - override val postsRepository: PostsRepository by lazy { - BlockingFakePostsRepository() - } - - override val interestsRepository: InterestsRepository by lazy { - FakeInterestsRepository() - } -} diff --git a/JetNews/app/src/sharedTest/java/com/example/jetnews/TestHelper.kt b/JetNews/app/src/sharedTest/java/com/example/jetnews/TestHelper.kt deleted file mode 100644 index 1e7e115d3b..0000000000 --- a/JetNews/app/src/sharedTest/java/com/example/jetnews/TestHelper.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetnews - -import android.content.Context -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import com.example.jetnews.ui.JetnewsApp - -/** - * Launches the app from a test context - */ -fun ComposeContentTestRule.launchJetNewsApp(context: Context) { - setContent { - JetnewsApp( - appContainer = TestAppContainer(context), - widthSizeClass = WindowWidthSizeClass.Compact - ) - } -} diff --git a/JetNews/app/src/test/resources/robolectric.properties b/JetNews/app/src/test/resources/robolectric.properties deleted file mode 100644 index 54ee43225e..0000000000 --- a/JetNews/app/src/test/resources/robolectric.properties +++ /dev/null @@ -1,8 +0,0 @@ -# Pin SDK to 30 since Robolectric does not currently support API 31: -# https://github.com/robolectric/robolectric/issues/6635 -sdk=30 -# Similar to Galaxy Nexus device profile -qualifiers=w360dp-h640dp-xhdpi - -# Workaround for https://github.com/robolectric/robolectric/issues/6593 -instrumentedPackages=androidx.loader.content From 16514f4af21279d7a6edb2c362548c4e7e6b87f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Alc=C3=A9rreca?= Date: Fri, 13 Sep 2024 17:44:35 +0000 Subject: [PATCH 2/2] How about I add the actual tests --- .../com/example/jetnews/HomeScreenTests.kt | 83 +++++++++++++++++++ .../java/com/example/jetnews/JetnewsTests.kt | 80 ++++++++++++++++++ .../com/example/jetnews/TestAppContainer.kt | 35 ++++++++ .../java/com/example/jetnews/TestHelper.kt | 34 ++++++++ 4 files changed, 232 insertions(+) create mode 100644 JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenTests.kt create mode 100644 JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsTests.kt create mode 100644 JetNews/app/src/androidTest/java/com/example/jetnews/TestAppContainer.kt create mode 100644 JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenTests.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenTests.kt new file mode 100644 index 0000000000..5d1432ad0d --- /dev/null +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenTests.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetnews + +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.example.jetnews.ui.home.HomeFeedScreen +import com.example.jetnews.ui.home.HomeUiState +import com.example.jetnews.ui.theme.JetnewsTheme +import com.example.jetnews.utils.ErrorMessage +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class HomeScreenTests { + + @get:Rule + val composeTestRule = createComposeRule() + + /** + * Checks that the Snackbar is shown when the HomeScreen data contains an error. + */ + @Test + fun postsContainError_snackbarShown() { + val snackbarHostState = SnackbarHostState() + composeTestRule.setContent { + JetnewsTheme { + + // When the Home screen receives data with an error + HomeFeedScreen( + uiState = HomeUiState.NoPosts( + isLoading = false, + errorMessages = listOf(ErrorMessage(0L, R.string.load_error)), + searchInput = "" + ), + showTopAppBar = false, + onToggleFavorite = {}, + onSelectPost = {}, + onRefreshPosts = {}, + onErrorDismiss = {}, + openDrawer = {}, + homeListLazyListState = rememberLazyListState(), + snackbarHostState = snackbarHostState, + onSearchInputChanged = {} + ) + } + } + + // Then the first message received in the Snackbar is an error message + runBlocking { + // snapshotFlow converts a State to a Kotlin Flow so we can observe it + // wait for the first a non-null `currentSnackbarData` + val actualSnackbarText = snapshotFlow { snackbarHostState.currentSnackbarData } + .filterNotNull().first().visuals.message + val expectedSnackbarText = InstrumentationRegistry.getInstrumentation() + .targetContext.resources.getString(R.string.load_error) + assertEquals(expectedSnackbarText, actualSnackbarText) + } + } +} diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsTests.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsTests.kt new file mode 100644 index 0000000000..bd02498cdd --- /dev/null +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsTests.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetnews + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.printToString +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.example.jetnews.data.posts.impl.manuel +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalTestApi::class) +@RunWith(AndroidJUnit4::class) +class JetnewsTests { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + @Before + fun setUp() { + // Using targetContext as the Context of the instrumentation code + composeTestRule.launchJetNewsApp(ApplicationProvider.getApplicationContext()) + } + + @Test + fun app_launches() { + composeTestRule + .onNodeWithText(composeTestRule.activity.getString(R.string.home_top_section_title)) + .assertExists() + } + + @Test + fun app_opensArticle() { + + println(composeTestRule.onRoot().printToString()) + composeTestRule.onAllNodes(hasText(manuel.name, substring = true))[0].performClick() + + println(composeTestRule.onRoot().printToString()) + try { + composeTestRule.onAllNodes(hasText("3 min read", substring = true))[0].assertExists() + } catch (e: AssertionError) { + println(composeTestRule.onRoot().printToString()) + throw e + } + } + + @Test + fun app_opensInterests() { + composeTestRule.onNodeWithContentDescription( + label = "Open navigation drawer", + useUnmergedTree = true + ).performClick() + composeTestRule.onNodeWithText("Interests").performClick() + composeTestRule.waitUntilAtLeastOneExists(hasText("Topics"), 5000L) + } +} diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/TestAppContainer.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/TestAppContainer.kt new file mode 100644 index 0000000000..2b8ed36c6c --- /dev/null +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/TestAppContainer.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetnews + +import android.content.Context +import com.example.jetnews.data.AppContainer +import com.example.jetnews.data.interests.InterestsRepository +import com.example.jetnews.data.interests.impl.FakeInterestsRepository +import com.example.jetnews.data.posts.PostsRepository +import com.example.jetnews.data.posts.impl.BlockingFakePostsRepository + +class TestAppContainer(private val context: Context) : AppContainer { + + override val postsRepository: PostsRepository by lazy { + BlockingFakePostsRepository() + } + + override val interestsRepository: InterestsRepository by lazy { + FakeInterestsRepository() + } +} diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt new file mode 100644 index 0000000000..1e7e115d3b --- /dev/null +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetnews + +import android.content.Context +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import com.example.jetnews.ui.JetnewsApp + +/** + * Launches the app from a test context + */ +fun ComposeContentTestRule.launchJetNewsApp(context: Context) { + setContent { + JetnewsApp( + appContainer = TestAppContainer(context), + widthSizeClass = WindowWidthSizeClass.Compact + ) + } +}