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 index 61210a1..62966fc 100644 --- a/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/AuthComposeInstrumentedTest.kt @@ -5,7 +5,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.fnives.test.showcase.compose.ComposeActivity 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.testutils.idling.ComposeMainDispatcherTestRule +import org.fnives.test.showcase.testutils.idling.ComposeNetworkSynchronizationTestRule import org.fnives.test.showcase.testutils.idling.anyResourceIdling import org.junit.Before import org.junit.Rule @@ -20,10 +21,11 @@ class AuthComposeInstrumentedTest : KoinTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule() + private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule(networkSynchronizationTestRule = ComposeNetworkSynchronizationTestRule(composeTestRule)) private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup - private val mainDispatcherTestRule = MainDispatcherTestRule() + private val mainDispatcherTestRule = ComposeMainDispatcherTestRule() private lateinit var robot: ComposeLoginRobot + private lateinit var screenRobot: ComposeScreenRobot @Rule @JvmField @@ -34,6 +36,7 @@ class AuthComposeInstrumentedTest : KoinTest { @Before fun setup() { robot = ComposeLoginRobot(composeTestRule) + screenRobot = ComposeScreenRobot(composeTestRule) } /** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */ @@ -44,19 +47,21 @@ class AuthComposeInstrumentedTest : KoinTest { ) composeTestRule.mainClock.advanceTimeBy(500L) composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() } - composeTestRule.waitForIdle() + screenRobot.assertAuthScreen() robot .setPassword("alma") .setUsername("banan") .assertUsername("banan") .assertPassword("alma") + composeTestRule.mainClock.autoAdvance = false robot.clickOnLogin() composeTestRule.mainClock.advanceTimeByFrame() robot.assertLoading() + composeTestRule.mainClock.autoAdvance = true -// mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() -// robot.assertNavigatedToHome() + composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() } + screenRobot.assertHomeScreen() } } \ No newline at end of file diff --git a/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeScreenRobot.kt b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeScreenRobot.kt new file mode 100644 index 0000000..a9c9542 --- /dev/null +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeScreenRobot.kt @@ -0,0 +1,19 @@ +package org.fnives.test.showcase.ui + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onNodeWithTag +import org.fnives.test.showcase.compose.screen.AppNavigationTag + +class ComposeScreenRobot( + private val composeTestRule: ComposeTestRule, +) { + + fun assertHomeScreen(): ComposeScreenRobot = apply { + composeTestRule.onNodeWithTag(AppNavigationTag.HomeScreen).assertIsDisplayed() + } + + fun assertAuthScreen(): ComposeScreenRobot = apply { + composeTestRule.onNodeWithTag(AppNavigationTag.AuthScreen).assertIsDisplayed() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/AppNavigation.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/AppNavigation.kt index 27890b3..a71b36c 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/AppNavigation.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/AppNavigation.kt @@ -5,16 +5,17 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.delay -import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase import org.fnives.test.showcase.compose.screen.auth.AuthScreen import org.fnives.test.showcase.compose.screen.auth.rememberAuthScreenState import org.fnives.test.showcase.compose.screen.home.HomeScreen import org.fnives.test.showcase.compose.screen.home.rememberHomeScreenState import org.fnives.test.showcase.compose.screen.splash.SplashScreen +import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase import org.koin.androidx.compose.get @Composable @@ -35,15 +36,22 @@ fun AppNavigation() { composable("Splash") { SplashScreen() } composable("Auth") { val authState = rememberAuthScreenState() - AuthScreen(authState) + AuthScreen(Modifier.testTag(AppNavigationTag.AuthScreen), authState) if (authState.navigateToHome?.consume() != null) { navController.navigate("Home") } } composable("Home") { - HomeScreen(rememberHomeScreenState { - navController.navigate("Auth") - }) + HomeScreen( + Modifier.testTag(AppNavigationTag.HomeScreen), + homeScreenState = rememberHomeScreenState( + onLogout = { navController.navigate("Auth") }) + ) } } +} + +object AppNavigationTag { + const val AuthScreen = "AppNavigationTag.AuthScreen" + const val HomeScreen = "AppNavigationTag.HomeScreen" } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt index 8c9e546..21ed15a 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt @@ -26,9 +26,10 @@ import org.fnives.test.showcase.R @Composable fun AuthScreen( + modifier: Modifier = Modifier, authScreenState: AuthScreenState = rememberAuthScreenState() ) { - ConstraintLayout(Modifier.fillMaxSize()) { + ConstraintLayout(modifier.fillMaxSize()) { val (title, credentials, snackbar, loading, login) = createRefs() Title( Modifier diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt index 843f62f..83ea9c6 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreenState.kt @@ -67,7 +67,6 @@ class AuthScreenState( LoginStatus.INVALID_USERNAME -> error = ErrorType.UNSUPPORTED_USERNAME LoginStatus.INVALID_PASSWORD -> error = ErrorType.UNSUPPORTED_PASSWORD } - println("asdasdasd: ${error.hashCode()}") } fun dismissError() { diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/home/HomeScreen.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/home/HomeScreen.kt index c397037..d09f4a6 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/home/HomeScreen.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/home/HomeScreen.kt @@ -26,9 +26,10 @@ import org.fnives.test.showcase.model.content.FavouriteContent @Composable fun HomeScreen( + modifier: Modifier = Modifier, homeScreenState: HomeScreenState = rememberHomeScreenState() ) { - Column(Modifier.fillMaxSize()) { + Column(modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { Title(Modifier.weight(1f)) Image( diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/MockServerScenarioSetupResetingTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/MockServerScenarioSetupResetingTestRule.kt index 421eb28..5998262 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/MockServerScenarioSetupResetingTestRule.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/MockServerScenarioSetupResetingTestRule.kt @@ -17,7 +17,7 @@ import org.koin.test.KoinTest */ class MockServerScenarioSetupResetingTestRule( private val reloadKoinModulesIfNecessaryTestRule: ReloadKoinModulesIfNecessaryTestRule = ReloadKoinModulesIfNecessaryTestRule(), - private val networkSynchronizationTestRule: NetworkSynchronizationTestRule = NetworkSynchronizationTestRule() + private val networkSynchronizationTestRule: TestRule = NetworkSynchronizationTestRule() ) : TestRule, KoinTest { lateinit var mockServerScenarioSetup: MockServerScenarioSetup diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeMainDispatcherTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeMainDispatcherTestRule.kt new file mode 100644 index 0000000..6d04ec1 --- /dev/null +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeMainDispatcherTestRule.kt @@ -0,0 +1,50 @@ +package org.fnives.test.showcase.testutils.idling + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import org.fnives.test.showcase.storage.database.DatabaseInitialization +import org.fnives.test.showcase.testutils.runOnUIAwaitOnCurrent +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +@OptIn(ExperimentalCoroutinesApi::class) +class ComposeMainDispatcherTestRule : TestRule { + + private lateinit var testDispatcher: TestDispatcher + + override fun apply(base: Statement, description: Description): Statement = + object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + val dispatcher = StandardTestDispatcher() + testDispatcher = dispatcher + DatabaseInitialization.dispatcher = dispatcher + base.evaluate() + } + } + + fun advanceUntilIdleWithIdlingResources() = runOnUIAwaitOnCurrent { + testDispatcher.advanceUntilIdleWithIdlingResources() + } + + fun advanceUntilIdle() = runOnUIAwaitOnCurrent { + testDispatcher.scheduler.advanceUntilIdle() + } + + fun advanceTimeBy(delayInMillis: Long) = runOnUIAwaitOnCurrent { + testDispatcher.scheduler.advanceTimeBy(delayInMillis) + } + + companion object { + fun TestDispatcher.advanceUntilIdleWithIdlingResources() { + scheduler.advanceUntilIdle() // advance until a request is sent + while (anyResourceIdling()) { // check if any request is in progress + awaitIdlingResources() // complete all requests and other idling resources + scheduler.advanceUntilIdle() // run coroutines after request is finished + } + scheduler.advanceUntilIdle() + } + } +} diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeNetworkSynchronizationTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeNetworkSynchronizationTestRule.kt new file mode 100644 index 0000000..2cfd4d2 --- /dev/null +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/ComposeNetworkSynchronizationTestRule.kt @@ -0,0 +1,66 @@ +package org.fnives.test.showcase.testutils.idling + +import androidx.annotation.CheckResult +import androidx.compose.ui.test.IdlingResource +import androidx.compose.ui.test.junit4.ComposeTestRule +import org.fnives.test.showcase.network.testutil.NetworkTestConfigurationHelper +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.koin.test.KoinTest + +class ComposeNetworkSynchronizationTestRule(private val composeTestRule: ComposeTestRule) : TestRule, KoinTest { + + private var disposable: Disposable? = null + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + disposable = registerNetworkingSynchronization() + try { + base.evaluate() + } finally { + dispose() + } + } + } + } + + fun dispose() = disposable?.dispose() + + @CheckResult + private fun registerNetworkingSynchronization(): Disposable { + val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients() + .associateBy(keySelector = { it.toString() }) + .map { (key, client) -> OkHttp3IdlingResource.create(key, client) } + .map { + ComposeIdlingResourceDisposable(composeTestRule, object : IdlingResource { + override val isIdleNow: Boolean + get() { + return it.isIdleNow + } + }) + } + + return CompositeDisposable(idlingResources) + } +} + + +private class ComposeIdlingResourceDisposable( + private val composeTestRule: ComposeTestRule, + private val idlingResource: IdlingResource +) : Disposable { + override var isDisposed: Boolean = false + private set + + init { + composeTestRule.registerIdlingResource(idlingResource) + } + + override fun dispose() { + if (isDisposed) return + isDisposed = true + composeTestRule.unregisterIdlingResource(idlingResource) + } +} \ No newline at end of file diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/MainDispatcherTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/MainDispatcherTestRule.kt index 07d3b74..6553624 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/MainDispatcherTestRule.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/MainDispatcherTestRule.kt @@ -22,13 +22,13 @@ class MainDispatcherTestRule : TestRule { @Throws(Throwable::class) override fun evaluate() { val dispatcher = StandardTestDispatcher() -// Dispatchers.setMain(dispatcher) + Dispatchers.setMain(dispatcher) testDispatcher = dispatcher DatabaseInitialization.dispatcher = dispatcher try { base.evaluate() } finally { -// Dispatchers.resetMain() + Dispatchers.resetMain() } } } diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronizationTestRule.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronizationTestRule.kt index 8294014..d2cf705 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronizationTestRule.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronizationTestRule.kt @@ -30,7 +30,7 @@ class NetworkSynchronizationTestRule : TestRule, KoinTest { @CheckResult private fun registerNetworkingSynchronization(): Disposable { - val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients()//.filterIndexed { index, okHttpClient -> index == 0 } + val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients() .associateBy(keySelector = { it.toString() }) .map { (key, client) -> client.asIdlingResource(key) } .map(::IdlingResourceDisposable)