Assert navigation
This commit is contained in:
parent
225fbed849
commit
d948d06378
11 changed files with 167 additions and 18 deletions
|
|
@ -5,7 +5,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.fnives.test.showcase.compose.ComposeActivity
|
import org.fnives.test.showcase.compose.ComposeActivity
|
||||||
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario
|
||||||
import org.fnives.test.showcase.testutils.MockServerScenarioSetupResetingTestRule
|
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.fnives.test.showcase.testutils.idling.anyResourceIdling
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -20,10 +21,11 @@ class AuthComposeInstrumentedTest : KoinTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val composeTestRule = createAndroidComposeRule<ComposeActivity>()
|
val composeTestRule = createAndroidComposeRule<ComposeActivity>()
|
||||||
|
|
||||||
private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule()
|
private val mockServerScenarioSetupTestRule = MockServerScenarioSetupResetingTestRule(networkSynchronizationTestRule = ComposeNetworkSynchronizationTestRule(composeTestRule))
|
||||||
private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
private val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
|
||||||
private val mainDispatcherTestRule = MainDispatcherTestRule()
|
private val mainDispatcherTestRule = ComposeMainDispatcherTestRule()
|
||||||
private lateinit var robot: ComposeLoginRobot
|
private lateinit var robot: ComposeLoginRobot
|
||||||
|
private lateinit var screenRobot: ComposeScreenRobot
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
|
|
@ -34,6 +36,7 @@ class AuthComposeInstrumentedTest : KoinTest {
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
robot = ComposeLoginRobot(composeTestRule)
|
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 */
|
/** 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.advanceTimeBy(500L)
|
||||||
composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() }
|
composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() }
|
||||||
composeTestRule.waitForIdle()
|
screenRobot.assertAuthScreen()
|
||||||
robot
|
robot
|
||||||
.setPassword("alma")
|
.setPassword("alma")
|
||||||
.setUsername("banan")
|
.setUsername("banan")
|
||||||
.assertUsername("banan")
|
.assertUsername("banan")
|
||||||
.assertPassword("alma")
|
.assertPassword("alma")
|
||||||
|
|
||||||
composeTestRule.mainClock.autoAdvance = false
|
composeTestRule.mainClock.autoAdvance = false
|
||||||
robot.clickOnLogin()
|
robot.clickOnLogin()
|
||||||
composeTestRule.mainClock.advanceTimeByFrame()
|
composeTestRule.mainClock.advanceTimeByFrame()
|
||||||
robot.assertLoading()
|
robot.assertLoading()
|
||||||
|
composeTestRule.mainClock.autoAdvance = true
|
||||||
|
|
||||||
// mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
|
composeTestRule.mainClock.advanceTimeUntil { anyResourceIdling() }
|
||||||
// robot.assertNavigatedToHome()
|
screenRobot.assertHomeScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,16 +5,17 @@ import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import kotlinx.coroutines.delay
|
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.AuthScreen
|
||||||
import org.fnives.test.showcase.compose.screen.auth.rememberAuthScreenState
|
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.HomeScreen
|
||||||
import org.fnives.test.showcase.compose.screen.home.rememberHomeScreenState
|
import org.fnives.test.showcase.compose.screen.home.rememberHomeScreenState
|
||||||
import org.fnives.test.showcase.compose.screen.splash.SplashScreen
|
import org.fnives.test.showcase.compose.screen.splash.SplashScreen
|
||||||
|
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
|
||||||
import org.koin.androidx.compose.get
|
import org.koin.androidx.compose.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -35,15 +36,22 @@ fun AppNavigation() {
|
||||||
composable("Splash") { SplashScreen() }
|
composable("Splash") { SplashScreen() }
|
||||||
composable("Auth") {
|
composable("Auth") {
|
||||||
val authState = rememberAuthScreenState()
|
val authState = rememberAuthScreenState()
|
||||||
AuthScreen(authState)
|
AuthScreen(Modifier.testTag(AppNavigationTag.AuthScreen), authState)
|
||||||
if (authState.navigateToHome?.consume() != null) {
|
if (authState.navigateToHome?.consume() != null) {
|
||||||
navController.navigate("Home")
|
navController.navigate("Home")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable("Home") {
|
composable("Home") {
|
||||||
HomeScreen(rememberHomeScreenState {
|
HomeScreen(
|
||||||
navController.navigate("Auth")
|
Modifier.testTag(AppNavigationTag.HomeScreen),
|
||||||
})
|
homeScreenState = rememberHomeScreenState(
|
||||||
|
onLogout = { navController.navigate("Auth") })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AppNavigationTag {
|
||||||
|
const val AuthScreen = "AppNavigationTag.AuthScreen"
|
||||||
|
const val HomeScreen = "AppNavigationTag.HomeScreen"
|
||||||
}
|
}
|
||||||
|
|
@ -26,9 +26,10 @@ import org.fnives.test.showcase.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthScreen(
|
fun AuthScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
authScreenState: AuthScreenState = rememberAuthScreenState()
|
authScreenState: AuthScreenState = rememberAuthScreenState()
|
||||||
) {
|
) {
|
||||||
ConstraintLayout(Modifier.fillMaxSize()) {
|
ConstraintLayout(modifier.fillMaxSize()) {
|
||||||
val (title, credentials, snackbar, loading, login) = createRefs()
|
val (title, credentials, snackbar, loading, login) = createRefs()
|
||||||
Title(
|
Title(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ class AuthScreenState(
|
||||||
LoginStatus.INVALID_USERNAME -> error = ErrorType.UNSUPPORTED_USERNAME
|
LoginStatus.INVALID_USERNAME -> error = ErrorType.UNSUPPORTED_USERNAME
|
||||||
LoginStatus.INVALID_PASSWORD -> error = ErrorType.UNSUPPORTED_PASSWORD
|
LoginStatus.INVALID_PASSWORD -> error = ErrorType.UNSUPPORTED_PASSWORD
|
||||||
}
|
}
|
||||||
println("asdasdasd: ${error.hashCode()}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismissError() {
|
fun dismissError() {
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ import org.fnives.test.showcase.model.content.FavouriteContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
homeScreenState: HomeScreenState = rememberHomeScreenState()
|
homeScreenState: HomeScreenState = rememberHomeScreenState()
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxSize()) {
|
Column(modifier.fillMaxSize()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Title(Modifier.weight(1f))
|
Title(Modifier.weight(1f))
|
||||||
Image(
|
Image(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import org.koin.test.KoinTest
|
||||||
*/
|
*/
|
||||||
class MockServerScenarioSetupResetingTestRule(
|
class MockServerScenarioSetupResetingTestRule(
|
||||||
private val reloadKoinModulesIfNecessaryTestRule: ReloadKoinModulesIfNecessaryTestRule = ReloadKoinModulesIfNecessaryTestRule(),
|
private val reloadKoinModulesIfNecessaryTestRule: ReloadKoinModulesIfNecessaryTestRule = ReloadKoinModulesIfNecessaryTestRule(),
|
||||||
private val networkSynchronizationTestRule: NetworkSynchronizationTestRule = NetworkSynchronizationTestRule()
|
private val networkSynchronizationTestRule: TestRule = NetworkSynchronizationTestRule()
|
||||||
) : TestRule, KoinTest {
|
) : TestRule, KoinTest {
|
||||||
|
|
||||||
lateinit var mockServerScenarioSetup: MockServerScenarioSetup
|
lateinit var mockServerScenarioSetup: MockServerScenarioSetup
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,13 +22,13 @@ class MainDispatcherTestRule : TestRule {
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
override fun evaluate() {
|
override fun evaluate() {
|
||||||
val dispatcher = StandardTestDispatcher()
|
val dispatcher = StandardTestDispatcher()
|
||||||
// Dispatchers.setMain(dispatcher)
|
Dispatchers.setMain(dispatcher)
|
||||||
testDispatcher = dispatcher
|
testDispatcher = dispatcher
|
||||||
DatabaseInitialization.dispatcher = dispatcher
|
DatabaseInitialization.dispatcher = dispatcher
|
||||||
try {
|
try {
|
||||||
base.evaluate()
|
base.evaluate()
|
||||||
} finally {
|
} finally {
|
||||||
// Dispatchers.resetMain()
|
Dispatchers.resetMain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class NetworkSynchronizationTestRule : TestRule, KoinTest {
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
private fun registerNetworkingSynchronization(): Disposable {
|
private fun registerNetworkingSynchronization(): Disposable {
|
||||||
val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients()//.filterIndexed { index, okHttpClient -> index == 0 }
|
val idlingResources = NetworkTestConfigurationHelper.getOkHttpClients()
|
||||||
.associateBy(keySelector = { it.toString() })
|
.associateBy(keySelector = { it.toString() })
|
||||||
.map { (key, client) -> client.asIdlingResource(key) }
|
.map { (key, client) -> client.asIdlingResource(key) }
|
||||||
.map(::IdlingResourceDisposable)
|
.map(::IdlingResourceDisposable)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue