Issue#11 Adjust SharedTests by using new TestDispatcher instead of deprecated TestDispatchers

This commit is contained in:
Gergely Hegedus 2022-01-23 21:18:30 +02:00
parent 46d9263742
commit 8ae94cfe92
4 changed files with 89 additions and 33 deletions

View file

@ -1,7 +1,10 @@
package org.fnives.test.showcase.testutils.configuration package org.fnives.test.showcase.testutils.configuration
import kotlinx.coroutines.Dispatchers 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.resetMain
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
import org.fnives.test.showcase.storage.database.DatabaseInitialization 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.runner.Description
import org.junit.runners.model.Statement import org.junit.runners.model.Statement
@OptIn(ExperimentalCoroutinesApi::class)
class TestCoroutineMainDispatcherTestRule : MainDispatcherTestRule { class TestCoroutineMainDispatcherTestRule : MainDispatcherTestRule {
private lateinit var testDispatcher: TestCoroutineDispatcher private lateinit var testDispatcher: TestDispatcher
override fun apply(base: Statement, description: Description): Statement = override fun apply(base: Statement, description: Description): Statement =
object : Statement() { object : Statement() {
@Throws(Throwable::class) @Throws(Throwable::class)
override fun evaluate() { override fun evaluate() {
val dispatcher = TestCoroutineDispatcher() val dispatcher = StandardTestDispatcher(TestCoroutineScheduler())
dispatcher.pauseDispatcher()
Dispatchers.setMain(dispatcher) Dispatchers.setMain(dispatcher)
testDispatcher = dispatcher testDispatcher = dispatcher
DatabaseInitialization.dispatcher = dispatcher DatabaseInitialization.dispatcher = dispatcher
@ -39,10 +42,10 @@ class TestCoroutineMainDispatcherTestRule : MainDispatcherTestRule {
} }
override fun advanceUntilIdle() { override fun advanceUntilIdle() {
testDispatcher.advanceUntilIdle() testDispatcher.scheduler.advanceUntilIdle()
} }
override fun advanceTimeBy(delayInMillis: Long) { override fun advanceTimeBy(delayInMillis: Long) {
testDispatcher.advanceTimeBy(delayInMillis) testDispatcher.scheduler.advanceTimeBy(delayInMillis)
} }
} }

View file

@ -4,15 +4,11 @@ import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource import androidx.test.espresso.IdlingResource
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadFor import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadFor
import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadUntilIdle import org.fnives.test.showcase.testutils.viewactions.LoopMainThreadUntilIdle
import java.util.concurrent.Executors
private val idleScope = CoroutineScope(Dispatchers.IO)
// workaround, issue with idlingResources is tracked here https://github.com/robolectric/robolectric/issues/4807 // workaround, issue with idlingResources is tracked here https://github.com/robolectric/robolectric/issues/4807
fun anyResourceIdling(): Boolean = !IdlingRegistry.getInstance().resources.all(IdlingResource::isIdleNow) fun anyResourceIdling(): Boolean = !IdlingRegistry.getInstance().resources.all(IdlingResource::isIdleNow)
@ -21,13 +17,14 @@ fun awaitIdlingResources() {
val idlingRegistry = IdlingRegistry.getInstance() val idlingRegistry = IdlingRegistry.getInstance()
if (idlingRegistry.resources.all(IdlingResource::isIdleNow)) return if (idlingRegistry.resources.all(IdlingResource::isIdleNow)) return
val executor = Executors.newSingleThreadExecutor()
var isIdle = false var isIdle = false
idleScope.launch { executor.submit {
do { do {
idlingRegistry.resources idlingRegistry.resources
.filterNot(IdlingResource::isIdleNow) .filterNot(IdlingResource::isIdleNow)
.forEach { idlingRegistry -> .forEach { idlingResource ->
idlingRegistry.awaitUntilIdle() idlingResource.awaitUntilIdle()
} }
} while (!idlingRegistry.resources.all(IdlingResource::isIdleNow)) } while (!idlingRegistry.resources.all(IdlingResource::isIdleNow))
isIdle = true isIdle = true
@ -35,23 +32,25 @@ fun awaitIdlingResources() {
while (!isIdle) { while (!isIdle) {
loopMainThreadFor(200L) loopMainThreadFor(200L)
} }
executor.shutdown()
} }
private suspend fun IdlingResource.awaitUntilIdle() { private fun IdlingResource.awaitUntilIdle() {
// using loop because some times, registerIdleTransitionCallback wasn't called // using loop because some times, registerIdleTransitionCallback wasn't called
while (true) { while (true) {
if (isIdleNow) return if (isIdleNow) return
delay(100) Thread.sleep(100L)
} }
} }
fun TestCoroutineDispatcher.advanceUntilIdleWithIdlingResources() { @OptIn(ExperimentalCoroutinesApi::class)
advanceUntilIdle() // advance until a request is sent fun TestDispatcher.advanceUntilIdleWithIdlingResources() {
scheduler.advanceUntilIdle() // advance until a request is sent
while (anyResourceIdling()) { // check if any request is in progress while (anyResourceIdling()) { // check if any request is in progress
awaitIdlingResources() // complete all requests and other idling resources 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() { fun loopMainThreadUntilIdleWithIdlingResources() {

View file

@ -78,7 +78,7 @@ class SplashActivityTest : KoinTest {
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
mainDispatcherTestRule.advanceTimeBy(500) mainDispatcherTestRule.advanceTimeBy(501)
splashRobot.assertHomeIsStarted() splashRobot.assertHomeIsStarted()
.assertAuthIsNotStarted() .assertAuthIsNotStarted()
@ -93,9 +93,36 @@ class SplashActivityTest : KoinTest {
activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java)
mainDispatcherTestRule.advanceTimeBy(500) mainDispatcherTestRule.advanceTimeBy(501)
splashRobot.assertAuthIsStarted() splashRobot.assertAuthIsStarted()
.assertHomeIsNotStarted() .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()
}
} }

View file

@ -1,6 +1,5 @@
package org.fnives.test.showcase.ui.splash package org.fnives.test.showcase.ui.splash
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
@ -26,10 +25,6 @@ class SplashActivityTest : KoinTest {
private val splashRobot: SplashRobot get() = robotTestRule.robot private val splashRobot: SplashRobot get() = robotTestRule.robot
@Rule
@JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Rule @Rule
@JvmField @JvmField
val robotTestRule = RobotTestRule(SplashRobot()) val robotTestRule = RobotTestRule(SplashRobot())
@ -61,14 +56,15 @@ class SplashActivityTest : KoinTest {
disposable.dispose() disposable.dispose()
} }
/** GIVEN loggedInState WHEN opened THEN MainActivity is started */ /** GIVEN loggedInState WHEN opened after some time THEN MainActivity is started */
@Test @Test
fun loggedInStateNavigatesToHome() { fun loggedInStateNavigatesToHome() {
SetupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) SetupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup)
activityScenario = ActivityScenario.launch(SplashActivity::class.java) activityScenario = ActivityScenario.launch(SplashActivity::class.java)
activityScenario.moveToState(Lifecycle.State.RESUMED)
mainDispatcherTestRule.advanceTimeBy(500) mainDispatcherTestRule.advanceTimeBy(501)
splashRobot.assertHomeIsStarted() splashRobot.assertHomeIsStarted()
.assertAuthIsNotStarted() .assertAuthIsNotStarted()
@ -76,16 +72,47 @@ class SplashActivityTest : KoinTest {
SetupLoggedInState.setupLogout() SetupLoggedInState.setupLogout()
} }
/** GIVEN loggedOffState WHEN opened THEN AuthActivity is started */ /** GIVEN loggedOffState WHEN opened after some time THEN AuthActivity is started */
@Test @Test
fun loggedOutStatesNavigatesToAuthentication() { fun loggedOutStatesNavigatesToAuthentication() {
SetupLoggedInState.setupLogout() SetupLoggedInState.setupLogout()
activityScenario = ActivityScenario.launch(SplashActivity::class.java) activityScenario = ActivityScenario.launch(SplashActivity::class.java)
activityScenario.moveToState(Lifecycle.State.RESUMED)
mainDispatcherTestRule.advanceTimeBy(500) mainDispatcherTestRule.advanceTimeBy(501)
splashRobot.assertAuthIsStarted() splashRobot.assertAuthIsStarted()
.assertHomeIsNotStarted() .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()
}
} }