-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# *Instrumentation tests* - Add `HiltApplicationTestRunner` to execute instrumented android tests with hilt - Introduce `ComposeContentActivity` in `design` module, in order to be able to start single screen instrumented tests in non-app modules - see `OnboardingScreenTest.kt` in `onboarding` module for example usage - Introduce test identifier (test tags) on relevant compose nodes of the screens in order to reference in UI tests - Introduce some extension helper functions on `ComposeTestRule` and `Modifier` - Add `OnboardingScreenTest` in onboarding module using fake repository (via a test hilt module) and screen display and click interaction - `Simulator` - Introduced the concept of simulators. Wrappers around `ComposeTestRule` with a DSL-like API to interact with a certain composable screen. - `app` - `AppNavigationTest` - Several test methods that invoke navigation via `Navigator` component and ensure display of the expected screen - `OnboardingFlowTest` tests the display of the initial onboarding screens and the navigation to the next expected one - Included several simulators that can also be reused in future tests - We can think of extracting all the simulators in a separate module for reusability among modules TODO: - Add Github action to run `connectedAndroidTest` (issue #21) in CI and include in codecov ## ♻️ Current situation & Problem *Link any open issues or pull requests (PRs) related to this PR. Please ensure that all non-trivial PRs are first tracked and discussed in an existing GitHub issue or discussion.* ## ⚙️ Release Notes *Add a bullet point list summary of the feature and possible migration guides if this is a breaking change so this section can be added to the release notes.* *Include code snippets that provide examples of the feature implemented or links to the documentation if it appends or changes the public interface.* ## 📚 Documentation *Please ensure that you properly document any additions in conformance to [Spezi Documentation Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).* *You can use this section to describe your solution, but we encourage contributors to document your reasoning and changes using in-line documentation.* ## ✅ Testing *Please ensure that the PR meets the testing requirements set by CodeCov and that new functionality is appropriately tested.* *This section describes important information about the tests and why some elements might not be testable.* ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [ ] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md). Co-authored-by: Paul Schmiedmayer <[email protected]>
- Loading branch information
1 parent
b9f4ae6
commit 9965c5a
Showing
39 changed files
with
1,026 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
app/src/androidTest/kotlin/edu/stanford/bdh/engagehf/AppNavigationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package edu.stanford.bdh.engagehf | ||
|
||
import android.Manifest | ||
import androidx.compose.ui.test.junit4.createAndroidComposeRule | ||
import androidx.test.rule.GrantPermissionRule | ||
import dagger.hilt.android.testing.HiltAndroidRule | ||
import dagger.hilt.android.testing.HiltAndroidTest | ||
import edu.stanford.bdh.engagehf.simulator.NavigatorSimulator | ||
import edu.stanford.spezi.core.navigation.Navigator | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import javax.inject.Inject | ||
|
||
@HiltAndroidTest | ||
class AppNavigationTest { | ||
|
||
@get:Rule(order = 1) | ||
val hiltRule = HiltAndroidRule(this) | ||
|
||
@get:Rule(order = 2) | ||
val composeTestRule = createAndroidComposeRule<MainActivity>() | ||
|
||
@get:Rule | ||
val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant( | ||
Manifest.permission.BLUETOOTH_CONNECT, | ||
Manifest.permission.BLUETOOTH_SCAN, | ||
) | ||
|
||
@Inject | ||
lateinit var navigator: Navigator | ||
|
||
@Before | ||
fun init() { | ||
hiltRule.inject() | ||
} | ||
|
||
@Test | ||
fun `test start destination`() { | ||
mainActivity { | ||
assertOnboardingIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to onboarding screen`() { | ||
mainActivity { | ||
navigateToOnboardingScreen() | ||
assertOnboardingIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to bluetooth screen`() { | ||
mainActivity { | ||
navigateToBluetoothScreen() | ||
assertBluetoothScreenIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to login screen`() { | ||
mainActivity { | ||
navigateToLoginScreen() | ||
assertLoginScreenIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to register screen`() { | ||
mainActivity { | ||
navigateToRegisterScreen() | ||
assertRegisterScreenIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to invitation code screen`() { | ||
mainActivity { | ||
navigateToInvitationCodeScreen() | ||
assertInvitationCodeScreenIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to sequential onboarding screen`() { | ||
mainActivity { | ||
navigateToSequentialOnboardingScreen() | ||
assertSequentialOnboardingScreenIsDisplayed() | ||
} | ||
} | ||
|
||
@Test | ||
fun `test navigation to consent screen`() { | ||
mainActivity { | ||
navigateToConsentScreen() | ||
assertConsentScreenIsDisplayed() | ||
} | ||
} | ||
|
||
private fun mainActivity(scope: NavigatorSimulator.() -> Unit) { | ||
NavigatorSimulator(composeTestRule, navigator).apply(scope) | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
app/src/androidTest/kotlin/edu/stanford/bdh/engagehf/OnboardingFlowTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package edu.stanford.bdh.engagehf | ||
|
||
import androidx.compose.ui.test.junit4.createAndroidComposeRule | ||
import dagger.hilt.android.testing.HiltAndroidRule | ||
import dagger.hilt.android.testing.HiltAndroidTest | ||
import edu.stanford.bdh.engagehf.simulator.OnboardingFlowSimulator | ||
import edu.stanford.spezi.module.onboarding.invitation.InvitationCodeRepository | ||
import edu.stanford.spezi.module.onboarding.onboarding.OnboardingRepository | ||
import edu.stanford.spezi.module.onboarding.sequential.SequentialOnboardingRepository | ||
import kotlinx.coroutines.test.runTest | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import javax.inject.Inject | ||
|
||
@HiltAndroidTest | ||
class OnboardingFlowTest { | ||
|
||
@get:Rule(order = 1) | ||
val hiltRule = HiltAndroidRule(this) | ||
|
||
@get:Rule(order = 2) | ||
val composeTestRule = createAndroidComposeRule<MainActivity>() | ||
|
||
@Inject | ||
lateinit var onboardingRepository: OnboardingRepository | ||
|
||
@Inject | ||
lateinit var sequentialOnboardingRepository: SequentialOnboardingRepository | ||
|
||
@Inject | ||
lateinit var invitationCodeRepository: InvitationCodeRepository | ||
|
||
@Before | ||
fun init() { | ||
hiltRule.inject() | ||
} | ||
|
||
@Test | ||
fun `it should show expected state of initial onboarding screen`() = runTest { | ||
val onboardingData = onboardingRepository.getOnboardingData().getOrThrow() | ||
onboardingFlow { | ||
onboardingScreen { | ||
assertDisplayed() | ||
assertTitle(text = onboardingData.title) | ||
assertSubtitle(text = onboardingData.subTitle) | ||
assertContinueButtonTitle(text = onboardingData.continueButtonText) | ||
|
||
onAreasList { | ||
assertDisplayed() | ||
onboardingData.areas.forEach { | ||
assertAreaTitle(title = it.title) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `it should navigate and display sequential onboarding correctly`() = runTest { | ||
val stepTitle = sequentialOnboardingRepository.getSequentialOnboardingData().steps.first().title | ||
onboardingFlow { | ||
onboardingScreen { | ||
clickContinueButton() | ||
} | ||
sequentialOnboarding { | ||
assertIsDisplayed() | ||
assertPagerIsDisplayed() | ||
assertPageIndicatorIsDisplayed() | ||
assertPageTitle(text = stepTitle) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `it should display and navigate invitation screen correctly`() = runTest { | ||
val steps = sequentialOnboardingRepository.getSequentialOnboardingData().steps | ||
onboardingFlow { | ||
onboardingScreen { | ||
clickContinueButton() | ||
} | ||
sequentialOnboarding { | ||
steps.forEach { | ||
assertPageTitle(text = it.title) | ||
clickForward() | ||
} | ||
} | ||
|
||
invitationCodeScreen { | ||
val screenData = invitationCodeRepository.getScreenData() | ||
assertTitle(text = screenData.title) | ||
assertDescription(text = screenData.description) | ||
assertIsDisplayed() | ||
assertMainButtonDisplayed() | ||
assertSecondaryButtonDisplayed() | ||
} | ||
} | ||
} | ||
|
||
private fun onboardingFlow(scope: OnboardingFlowSimulator.() -> Unit) { | ||
OnboardingFlowSimulator(composeTestRule).apply(scope) | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
...c/androidTest/kotlin/edu/stanford/bdh/engagehf/simulator/InvitationCodeScreenSimulator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package edu.stanford.bdh.engagehf.simulator | ||
|
||
import androidx.compose.ui.test.assertIsDisplayed | ||
import androidx.compose.ui.test.assertTextEquals | ||
import androidx.compose.ui.test.junit4.ComposeTestRule | ||
import edu.stanford.spezi.core.testing.onNodeWithIdentifier | ||
import edu.stanford.spezi.core.testing.waitNode | ||
import edu.stanford.spezi.module.onboarding.invitation.InvitationCodeScreenTestIdentifier | ||
|
||
class InvitationCodeScreenSimulator( | ||
private val composeTestRule: ComposeTestRule, | ||
) { | ||
private val root = composeTestRule.onNodeWithIdentifier(InvitationCodeScreenTestIdentifier.ROOT) | ||
private val title = | ||
composeTestRule.onNodeWithIdentifier(InvitationCodeScreenTestIdentifier.TITLE) | ||
private val description = composeTestRule.onNodeWithIdentifier( | ||
InvitationCodeScreenTestIdentifier.DESCRIPTION | ||
) | ||
private val mainButton = | ||
composeTestRule.onNodeWithIdentifier(InvitationCodeScreenTestIdentifier.MAIN_ACTION_BUTTON) | ||
private val secondaryButton = | ||
composeTestRule.onNodeWithIdentifier(InvitationCodeScreenTestIdentifier.SECONDARY_ACTION_BUTTON) | ||
|
||
fun assertIsDisplayed() { | ||
composeTestRule.waitNode(InvitationCodeScreenTestIdentifier.ROOT) | ||
root.assertIsDisplayed() | ||
} | ||
|
||
fun assertTitle(text: String) { | ||
title | ||
.assertIsDisplayed() | ||
.assertTextEquals(text) | ||
} | ||
|
||
fun assertDescription(text: String) { | ||
description | ||
.assertIsDisplayed() | ||
.assertTextEquals(text) | ||
} | ||
|
||
fun assertMainButtonDisplayed() { | ||
mainButton.assertIsDisplayed() | ||
} | ||
|
||
fun assertSecondaryButtonDisplayed() { | ||
secondaryButton.assertIsDisplayed() | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
app/src/androidTest/kotlin/edu/stanford/bdh/engagehf/simulator/NavigatorSimulator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package edu.stanford.bdh.engagehf.simulator | ||
|
||
import androidx.compose.ui.test.assertIsDisplayed | ||
import androidx.compose.ui.test.junit4.ComposeTestRule | ||
import edu.stanford.bdh.engagehf.bluetooth.screen.BluetoothScreenTestIdentifier | ||
import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent | ||
import edu.stanford.spezi.core.navigation.Navigator | ||
import edu.stanford.spezi.core.testing.onAllNodes | ||
import edu.stanford.spezi.core.testing.onNodeWithIdentifier | ||
import edu.stanford.spezi.core.utils.TestIdentifier | ||
import edu.stanford.spezi.module.account.AccountNavigationEvent | ||
import edu.stanford.spezi.module.account.login.LoginScreenTestIdentifier | ||
import edu.stanford.spezi.module.account.register.RegisterScreenTestIdentifier | ||
import edu.stanford.spezi.module.onboarding.OnboardingNavigationEvent | ||
import edu.stanford.spezi.module.onboarding.consent.ConsentScreenTestIdentifier | ||
import edu.stanford.spezi.module.onboarding.invitation.InvitationCodeScreenTestIdentifier | ||
import edu.stanford.spezi.module.onboarding.onboarding.OnboardingScreenTestIdentifier | ||
import edu.stanford.spezi.module.onboarding.sequential.SequentialOnboardingScreenTestIdentifier | ||
|
||
class NavigatorSimulator( | ||
private val composeTestRule: ComposeTestRule, | ||
private val navigator: Navigator, | ||
) { | ||
private val onboarding = | ||
composeTestRule.onNodeWithIdentifier(OnboardingScreenTestIdentifier.ROOT) | ||
private val register = composeTestRule.onNodeWithIdentifier(RegisterScreenTestIdentifier.ROOT) | ||
private val login = composeTestRule.onNodeWithIdentifier(LoginScreenTestIdentifier.ROOT) | ||
private val invitation = | ||
composeTestRule.onNodeWithIdentifier(InvitationCodeScreenTestIdentifier.ROOT) | ||
private val sequential = | ||
composeTestRule.onNodeWithIdentifier(SequentialOnboardingScreenTestIdentifier.ROOT) | ||
private val bluetooth = composeTestRule.onNodeWithIdentifier(BluetoothScreenTestIdentifier.ROOT) | ||
private val consent = composeTestRule.onNodeWithIdentifier(ConsentScreenTestIdentifier.ROOT) | ||
|
||
fun assertOnboardingIsDisplayed() { | ||
waitNode(OnboardingScreenTestIdentifier.ROOT) | ||
onboarding.assertIsDisplayed() | ||
} | ||
|
||
fun assertBluetoothScreenIsDisplayed() { | ||
waitNode(BluetoothScreenTestIdentifier.ROOT) | ||
bluetooth.assertIsDisplayed() | ||
} | ||
|
||
fun assertLoginScreenIsDisplayed() { | ||
waitNode(LoginScreenTestIdentifier.ROOT) | ||
login.assertIsDisplayed() | ||
} | ||
|
||
fun assertRegisterScreenIsDisplayed() { | ||
waitNode(RegisterScreenTestIdentifier.ROOT) | ||
register.assertIsDisplayed() | ||
} | ||
|
||
fun assertInvitationCodeScreenIsDisplayed() { | ||
waitNode(InvitationCodeScreenTestIdentifier.ROOT) | ||
invitation.assertIsDisplayed() | ||
} | ||
|
||
fun assertSequentialOnboardingScreenIsDisplayed() { | ||
waitNode(SequentialOnboardingScreenTestIdentifier.ROOT) | ||
sequential.assertIsDisplayed() | ||
} | ||
|
||
fun assertConsentScreenIsDisplayed() { | ||
waitNode(ConsentScreenTestIdentifier.ROOT) | ||
consent.assertIsDisplayed() | ||
} | ||
|
||
fun navigateToBluetoothScreen() { | ||
navigator.navigateTo(AppNavigationEvent.BluetoothScreen) | ||
} | ||
|
||
fun navigateToOnboardingScreen() { | ||
navigator.navigateTo(OnboardingNavigationEvent.OnboardingScreen) | ||
} | ||
|
||
fun navigateToLoginScreen() { | ||
navigator.navigateTo(AccountNavigationEvent.LoginScreen(false)) | ||
} | ||
|
||
fun navigateToRegisterScreen() { | ||
navigator.navigateTo(AccountNavigationEvent.RegisterScreen()) | ||
} | ||
|
||
fun navigateToInvitationCodeScreen() { | ||
navigator.navigateTo(OnboardingNavigationEvent.InvitationCodeScreen) | ||
} | ||
|
||
fun navigateToSequentialOnboardingScreen() { | ||
navigator.navigateTo(OnboardingNavigationEvent.SequentialOnboardingScreen) | ||
} | ||
|
||
fun navigateToConsentScreen() { | ||
navigator.navigateTo(OnboardingNavigationEvent.ConsentScreen) | ||
} | ||
|
||
private fun waitNode(testIdentifier: TestIdentifier) { | ||
composeTestRule.waitUntil { | ||
composeTestRule.onAllNodes(testIdentifier).fetchSemanticsNodes().isNotEmpty() | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
app/src/androidTest/kotlin/edu/stanford/bdh/engagehf/simulator/OnboardingFlowSimulator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package edu.stanford.bdh.engagehf.simulator | ||
|
||
import androidx.compose.ui.test.junit4.ComposeTestRule | ||
|
||
class OnboardingFlowSimulator( | ||
composeTestRule: ComposeTestRule, | ||
) { | ||
private val onboardingScreenSimulator = OnboardingScreenSimulator(composeTestRule) | ||
private val sequentialOnboardingScreenSimulator = SequentialOnboardingScreenSimulator(composeTestRule) | ||
private val invitationCodeScreenSimulator = InvitationCodeScreenSimulator(composeTestRule) | ||
|
||
fun onboardingScreen(scope: OnboardingScreenSimulator.() -> Unit) { | ||
onboardingScreenSimulator.apply(scope) | ||
} | ||
|
||
fun sequentialOnboarding(scope: SequentialOnboardingScreenSimulator.() -> Unit) { | ||
sequentialOnboardingScreenSimulator.apply(scope) | ||
} | ||
|
||
fun invitationCodeScreen(scope: InvitationCodeScreenSimulator.() -> Unit) { | ||
invitationCodeScreenSimulator.apply(scope) | ||
} | ||
} |
Oops, something went wrong.