diff --git a/app/build.gradle b/app/build.gradle index 3fa3f9c..cb29971 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -132,6 +132,9 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$testing_espresso_version" androidTestImplementation "androidx.test.espresso:espresso-intents:$testing_espresso_version" androidTestImplementation "androidx.test.espresso:espresso-contrib:$testing_espresso_version" + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$androidx_compose" + testImplementation "androidx.compose.ui:ui-test-junit4:$androidx_compose" +// debugImplementation "androidx.compose.ui:ui-test-manifest:$androidx_compose" androidTestImplementation project(':mockserver') androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" diff --git a/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt b/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt new file mode 100644 index 0000000..82db8f8 --- /dev/null +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt @@ -0,0 +1,67 @@ +package org.fnives.test.showcase.ui + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario +import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule +import org.fnives.test.showcase.testutils.idling.MainDispatcherTestRule +import org.fnives.test.showcase.ui.compose.ComposeActivity +import org.fnives.test.showcase.ui.compose.TestShowCaseApp +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.koin.test.KoinTest + +@RunWith(AndroidJUnit4::class) +class AuthComposeInstrumentedTest : KoinTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + +// private lateinit var activityScenario: ActivityScenario + + private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule() + private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup + private val mainDispatcherTestRule = MainDispatcherTestRule() + private lateinit var robot: ComposeLoginRobot + + @Rule + @JvmField + val ruleOrder: RuleChain = RuleChain.outerRule(mockServerScenarioSetupTestRule) + .around(mainDispatcherTestRule) + + @Before + fun setup() { + robot = ComposeLoginRobot(composeTestRule) + composeTestRule.setContent { + TestShowCaseApp() + } + } + +// @After +// fun tearDown() { +// activityScenario.safeClose() +// } + + /** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */ + @Test + fun properLoginResultsInNavigationToHome() { + mockServerScenarioSetup.setScenario( + AuthScenario.Success(password = "alma", username = "banan") + ) + composeTestRule.waitForIdle() + robot + .setPassword("alma") + .setUsername("banan") + .assertPassword("alma") + .assertUsername("banan") + .clickOnLogin() +// .assertLoadingBeforeRequests() + + mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() +// robot.assertNavigatedToHome() + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt new file mode 100644 index 0000000..95e9b4a --- /dev/null +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt @@ -0,0 +1,33 @@ +package org.fnives.test.showcase.ui + +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import org.fnives.test.showcase.ui.compose.screen.auth.AuthScreenTag + +class ComposeLoginRobot( + private val composeTestRule: ComposeTestRule, +) { + + fun setUsername(username: String): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.UsernameInput).performTextInput(username) + } + + fun setPassword(password: String): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.PasswordInput).performTextInput(password) + } + + fun assertPassword(password: String): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.PasswordInput).assertTextEquals(password) + } + + fun assertUsername(username: String): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.UsernameInput).assertTextEquals(username) + } + + fun clickOnLogin(): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.LoginButton).performClick() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt index bcecc68..f8b60d1 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable import com.google.accompanist.insets.ProvideWindowInsets import org.fnives.test.showcase.ui.compose.screen.AppNavigation @@ -12,11 +13,16 @@ class ComposeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - ProvideWindowInsets { - MaterialTheme { - AppNavigation() - } - } + TestShowCaseApp() + } + } +} + +@Composable +fun TestShowCaseApp() { + ProvideWindowInsets { + MaterialTheme { + AppNavigation() } } } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt index 2bfe613..081d080 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -69,7 +70,7 @@ private fun CredentialsFields(authScreenState: AuthScreenState, modifier: Modifi placeholder = { Text(text = stringResource(id = R.string.username)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email, imeAction = ImeAction.Next), onValueChange = { authScreenState.onUsernameChanged(it) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().testTag(AuthScreenTag.UsernameInput) ) OutlinedTextField( value = authScreenState.password, @@ -85,6 +86,7 @@ private fun CredentialsFields(authScreenState: AuthScreenState, modifier: Modifi modifier = Modifier .fillMaxWidth() .padding(top = 16.dp) + .testTag(AuthScreenTag.PasswordInput) ) } } @@ -112,7 +114,7 @@ private fun Snackbar(authScreenState: AuthScreenState, modifier: Modifier = Modi @Composable private fun LoginButton(modifier: Modifier = Modifier, onClick: () -> Unit) { Box(modifier) { - Button(onClick = onClick, Modifier.fillMaxWidth()) { + Button(onClick = onClick, Modifier.fillMaxWidth().testTag(AuthScreenTag.LoginButton)) { Text(text = "Login") } } @@ -132,4 +134,10 @@ private fun AuthScreenState.ErrorType.stringResId() = when (this) { AuthScreenState.ErrorType.GENERAL_NETWORK_ERROR -> R.string.something_went_wrong AuthScreenState.ErrorType.UNSUPPORTED_USERNAME -> R.string.username_is_invalid AuthScreenState.ErrorType.UNSUPPORTED_PASSWORD -> R.string.password_is_invalid -} \ No newline at end of file +} + +object AuthScreenTag { + const val UsernameInput = "AuthScreenTag.UsernameInput" + const val PasswordInput = "AuthScreenTag.PasswordInput" + const val LoginButton = "AuthScreenTag.LoginButton" +}