diff --git a/app/src/robolectricTest/java/org/fnives/test/showcase/testutils/configuration/TestCoroutineMainDispatcherTestRule.kt b/app/src/robolectricTest/java/org/fnives/test/showcase/testutils/configuration/TestCoroutineMainDispatcherTestRule.kt index 4706794..5460a89 100644 --- a/app/src/robolectricTest/java/org/fnives/test/showcase/testutils/configuration/TestCoroutineMainDispatcherTestRule.kt +++ b/app/src/robolectricTest/java/org/fnives/test/showcase/testutils/configuration/TestCoroutineMainDispatcherTestRule.kt @@ -1,7 +1,10 @@ package org.fnives.test.showcase.testutils.configuration import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.fnives.test.showcase.storage.database.DatabaseInitialization @@ -9,16 +12,16 @@ import org.fnives.test.showcase.testutils.idling.advanceUntilIdleWithIdlingResou import org.junit.runner.Description import org.junit.runners.model.Statement +@OptIn(ExperimentalCoroutinesApi::class) class TestCoroutineMainDispatcherTestRule : MainDispatcherTestRule { - private lateinit var testDispatcher: TestCoroutineDispatcher + private lateinit var testDispatcher: TestDispatcher override fun apply(base: Statement, description: Description): Statement = object : Statement() { @Throws(Throwable::class) override fun evaluate() { - val dispatcher = TestCoroutineDispatcher() - dispatcher.pauseDispatcher() + val dispatcher = StandardTestDispatcher(TestCoroutineScheduler()) Dispatchers.setMain(dispatcher) testDispatcher = dispatcher DatabaseInitialization.dispatcher = dispatcher @@ -39,10 +42,10 @@ class TestCoroutineMainDispatcherTestRule : MainDispatcherTestRule { } override fun advanceUntilIdle() { - testDispatcher.advanceUntilIdle() + testDispatcher.scheduler.advanceUntilIdle() } override fun advanceTimeBy(delayInMillis: Long) { - testDispatcher.advanceTimeBy(delayInMillis) + testDispatcher.scheduler.advanceTimeBy(delayInMillis) } } diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/awaitIdlingResources.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/awaitIdlingResources.kt index 896255a..80a7d15 100644 --- a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/awaitIdlingResources.kt +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/awaitIdlingResources.kt @@ -4,15 +4,11 @@ import androidx.test.espresso.Espresso import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource import androidx.test.espresso.matcher.ViewMatchers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestDispatcher import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadFor import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadUntilIdle - -private val idleScope = CoroutineScope(Dispatchers.IO) +import java.util.concurrent.Executors // workaround, issue with idlingResources is tracked here https://github.com/robolectric/robolectric/issues/4807 fun anyResourceIdling(): Boolean = !IdlingRegistry.getInstance().resources.all(IdlingResource::isIdleNow) @@ -21,13 +17,14 @@ fun awaitIdlingResources() { val idlingRegistry = IdlingRegistry.getInstance() if (idlingRegistry.resources.all(IdlingResource::isIdleNow)) return + val executor = Executors.newSingleThreadExecutor() var isIdle = false - idleScope.launch { + executor.submit { do { idlingRegistry.resources .filterNot(IdlingResource::isIdleNow) - .forEach { idlingRegistry -> - idlingRegistry.awaitUntilIdle() + .forEach { idlingResource -> + idlingResource.awaitUntilIdle() } } while (!idlingRegistry.resources.all(IdlingResource::isIdleNow)) isIdle = true @@ -35,23 +32,25 @@ fun awaitIdlingResources() { while (!isIdle) { loopMainThreadFor(200L) } + executor.shutdown() } -private suspend fun IdlingResource.awaitUntilIdle() { +private fun IdlingResource.awaitUntilIdle() { // using loop because some times, registerIdleTransitionCallback wasn't called while (true) { if (isIdleNow) return - delay(100) + Thread.sleep(100L) } } -fun TestCoroutineDispatcher.advanceUntilIdleWithIdlingResources() { - advanceUntilIdle() // advance until a request is sent +@OptIn(ExperimentalCoroutinesApi::class) +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 - advanceUntilIdle() // run coroutines after request is finished + scheduler.advanceUntilIdle() // run coroutines after request is finished } - advanceUntilIdle() + scheduler.advanceUntilIdle() } fun loopMainThreadUntilIdleWithIdlingResources() { diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt index 1caf222..33c3be2 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt @@ -78,7 +78,7 @@ class SplashActivityTest : KoinTest { activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) - mainDispatcherTestRule.advanceTimeBy(500) + mainDispatcherTestRule.advanceTimeBy(501) splashRobot.assertHomeIsStarted() .assertAuthIsNotStarted() @@ -93,9 +93,36 @@ class SplashActivityTest : KoinTest { activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) - mainDispatcherTestRule.advanceTimeBy(500) + mainDispatcherTestRule.advanceTimeBy(501) splashRobot.assertAuthIsStarted() .assertHomeIsNotStarted() } + + @Test + fun loggedOutStatesNotEnoughTime() { + setupLoggedInState.setupLogout() + + activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) + + mainDispatcherTestRule.advanceTimeBy(10) + + splashRobot.assertAuthIsNotStarted() + .assertHomeIsNotStarted() + } + + /** GIVEN loggedInState and not enough time WHEN opened THEN no activity is started */ + @Test + fun loggedInStatesNotEnoughTime() { + setupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + + activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) + + mainDispatcherTestRule.advanceTimeBy(10) + + splashRobot.assertHomeIsNotStarted() + .assertAuthIsNotStarted() + + setupLoggedInState.setupLogout() + } } diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt index c7b1a1a..74e202f 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt @@ -1,6 +1,5 @@ package org.fnives.test.showcase.ui.splash -import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -26,10 +25,6 @@ class SplashActivityTest : KoinTest { private val splashRobot: SplashRobot get() = robotTestRule.robot - @Rule - @JvmField - val instantTaskExecutorRule = InstantTaskExecutorRule() - @Rule @JvmField val robotTestRule = RobotTestRule(SplashRobot()) @@ -61,14 +56,15 @@ class SplashActivityTest : KoinTest { disposable.dispose() } - /** GIVEN loggedInState WHEN opened THEN MainActivity is started */ + /** GIVEN loggedInState WHEN opened after some time THEN MainActivity is started */ @Test fun loggedInStateNavigatesToHome() { SetupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) activityScenario = ActivityScenario.launch(SplashActivity::class.java) + activityScenario.moveToState(Lifecycle.State.RESUMED) - mainDispatcherTestRule.advanceTimeBy(500) + mainDispatcherTestRule.advanceTimeBy(501) splashRobot.assertHomeIsStarted() .assertAuthIsNotStarted() @@ -76,16 +72,47 @@ class SplashActivityTest : KoinTest { SetupLoggedInState.setupLogout() } - /** GIVEN loggedOffState WHEN opened THEN AuthActivity is started */ + /** GIVEN loggedOffState WHEN opened after some time THEN AuthActivity is started */ @Test fun loggedOutStatesNavigatesToAuthentication() { SetupLoggedInState.setupLogout() activityScenario = ActivityScenario.launch(SplashActivity::class.java) + activityScenario.moveToState(Lifecycle.State.RESUMED) - mainDispatcherTestRule.advanceTimeBy(500) + mainDispatcherTestRule.advanceTimeBy(501) splashRobot.assertAuthIsStarted() .assertHomeIsNotStarted() } + + /** GIVEN loggedOffState and not enough time WHEN opened THEN no activity is started */ + @Test + fun loggedOutStatesNotEnoughTime() { + SetupLoggedInState.setupLogout() + + activityScenario = ActivityScenario.launch(SplashActivity::class.java) + activityScenario.moveToState(Lifecycle.State.RESUMED) + + mainDispatcherTestRule.advanceTimeBy(10) + + splashRobot.assertAuthIsNotStarted() + .assertHomeIsNotStarted() + } + + /** GIVEN loggedInState and not enough time WHEN opened THEN no activity is started */ + @Test + fun loggedInStatesNotEnoughTime() { + SetupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) + + activityScenario = ActivityScenario.launch(SplashActivity::class.java) + activityScenario.moveToState(Lifecycle.State.RESUMED) + + mainDispatcherTestRule.advanceTimeBy(10) + + splashRobot.assertHomeIsNotStarted() + .assertAuthIsNotStarted() + + SetupLoggedInState.setupLogout() + } }