Assert navigation

This commit is contained in:
Alex Gabor 2022-04-01 14:59:36 +03:00
parent 225fbed849
commit d948d06378
11 changed files with 167 additions and 18 deletions

View file

@ -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<ComposeActivity>()
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()
}
}

View file

@ -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()
}
}

View file

@ -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"
}

View file

@ -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

View file

@ -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() {

View file

@ -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(

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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)
}
}

View file

@ -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()
}
}
}

View file

@ -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)