Skip to content

Commit

Permalink
Merge pull request #26 from azrael8576/test/ui-test
Browse files Browse the repository at this point in the history
[Test] Add app navigation end to end test
  • Loading branch information
azrael8576 authored Dec 10, 2023
2 parents 67bc59a + ba5ba32 commit 3b2ce1c
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
83 changes: 83 additions & 0 deletions app/src/androidTest/java/com/wei/picquest/ui/NavigationRobot.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import androidx.annotation.StringRes
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.wei.picquest.MainActivity
import kotlin.properties.ReadOnlyProperty

/**
* Robot for [NavigationTest].
*
* 遵循此模型,找到測試使用者介面元素、檢查其屬性、和透過測試規則執行動作:
* composeTestRule{.finder}{.assertion}{.action}
*
* Testing cheatsheet:
* https://developer.android.com/jetpack/compose/testing-cheatsheet
*/
internal fun navigationRobot(
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>,
func: NavigationRobot.() -> Unit,
) = NavigationRobot(composeTestRule).apply(func)

internal open class NavigationRobot(
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>,
) {
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }

// The strings used for matching in these tests
private val home by composeTestRule.stringResource(com.wei.picquest.R.string.home)
private val photo by composeTestRule.stringResource(com.wei.picquest.R.string.photo)
private val video by composeTestRule.stringResource(com.wei.picquest.R.string.video)
private val contactMe by composeTestRule.stringResource(com.wei.picquest.R.string.contact_me)

private val navHome by lazy {
composeTestRule.onNodeWithContentDescription(
home,
useUnmergedTree = true,
)
}
private val navPhoto by lazy {
composeTestRule.onNodeWithContentDescription(
photo,
useUnmergedTree = true,
)
}
private val navVideo by lazy {
composeTestRule.onNodeWithContentDescription(
video,
useUnmergedTree = true,
)
}
private val navContactMe by lazy {
composeTestRule.onNodeWithContentDescription(
contactMe,
useUnmergedTree = true,
)
}

internal fun clickNavHome() {
navHome.performClick()
// 等待任何動畫完成
composeTestRule.waitForIdle()
}

internal fun clickNavPhoto() {
navPhoto.performClick()
// 等待任何動畫完成
composeTestRule.waitForIdle()
}

internal fun clickVideo() {
navVideo.performClick()
// 等待任何動畫完成
composeTestRule.waitForIdle()
}

internal fun clickNavContactMe() {
navContactMe.performClick()
// 等待任何動畫完成
composeTestRule.waitForIdle()
}
}
87 changes: 87 additions & 0 deletions app/src/androidTest/java/com/wei/picquest/ui/NavigationTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.wei.picquest.ui

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.espresso.Espresso
import androidx.test.espresso.NoActivityResumedException
import com.wei.picquest.MainActivity
import com.wei.picquest.ui.robot.homeEndToEndRobot
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import navigationRobot
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

/**
* Tests all the navigation flows that are handled by the navigation library.
*/
@HiltAndroidTest
class NavigationTest {

/**
* Manages the components' state and is used to perform injection on your test
*/
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

/**
* Use the primary activity to initialize the app normally.
*/
@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Before
fun setup() = hiltRule.inject()

@Test
fun firstScreen_isHome() {
homeEndToEndRobot(composeTestRule) {
verifyImportantNoteDisplayed()
}
}

/*
* When pressing back from any top level destination except "Home", the app navigates back
* to the "Home" destination, no matter which destinations you visited in between.
*/
@Test
fun navigationBar_backFromAnyDestination_returnsToHome() {
homeEndToEndRobot(composeTestRule) {
navigationRobot(composeTestRule) {
// GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown.
clickNavContactMe()
// WHEN the user uses the system button/gesture to go back
Espresso.pressBack()
}
verifyImportantNoteDisplayed()
}
}

/*
* There should always be at most one instance of a top-level destination at the same time.
*/
@Test(expected = NoActivityResumedException::class)
fun topDestination_back_quitsApp() {
homeEndToEndRobot(composeTestRule) {
navigationRobot(composeTestRule) {
// GIVEN the user navigates to the Contact Me destination
clickNavContactMe()
// and then navigates to the Home destination
clickNavHome()
// WHEN the user uses the system button/gesture to go back
Espresso.pressBack()
// THEN the app quits
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.wei.picquest.ui.robot

import androidx.annotation.StringRes
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.wei.picquest.MainActivity
import kotlin.properties.ReadOnlyProperty
import com.wei.picquest.feature.home.R as FeatureHomeR

/**
* Screen Robot for End To End Test.
*
* 遵循此模型,找到測試使用者介面元素、檢查其屬性、和透過測試規則執行動作:
* composeTestRule{.finder}{.assertion}{.action}
*
* Testing cheatsheet:
* https://developer.android.com/jetpack/compose/testing-cheatsheet
*/
internal fun homeEndToEndRobot(
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>,
func: HomeEndToEndRobot.() -> Unit,
) = HomeEndToEndRobot(composeTestRule).apply(func)

internal open class HomeEndToEndRobot(
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>,
) {
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }

// The strings used for matching in these tests
private val importantNoteDescription by composeTestRule.stringResource(FeatureHomeR.string.important_note)

private val importantNote by lazy {
composeTestRule.onNode(hasContentDescription(importantNoteDescription))
}

fun verifyImportantNoteDisplayed() {
importantNote.assertExists().assertIsDisplayed()
}
}

0 comments on commit 3b2ce1c

Please sign in to comment.