Last active
November 10, 2022 06:53
-
-
Save akueisara/d7329d79630c709346ffceafd211ded2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.view.View | |
import androidx.databinding.DataBindingUtil | |
import androidx.databinding.ViewDataBinding | |
import androidx.fragment.app.Fragment | |
import androidx.fragment.app.FragmentActivity | |
import androidx.fragment.app.testing.FragmentScenario | |
import androidx.test.core.app.ActivityScenario | |
import androidx.test.espresso.IdlingResource | |
import java.util.* | |
// A custom idling resource for data binding | |
class DataBindingIdlingResource : IdlingResource { | |
// List of registered callbacks | |
private val idlingCallbacks = mutableListOf<IdlingResource.ResourceCallback>() | |
// Give it a unique id to work around an Espresso bug where you cannot register/unregister | |
// an idling resource with the same name. | |
private val id = UUID.randomUUID().toString() | |
// Holds whether isIdle was called and the result was false. We track this to avoid calling | |
// onTransitionToIdle callbacks if Espresso never thought we were idle in the first place. | |
private var wasNotIdle = false | |
lateinit var activity: FragmentActivity | |
override fun getName() = "DataBinding $id" | |
override fun isIdleNow(): Boolean { | |
val idle = !getBindings().any { it.hasPendingBindings() } | |
@Suppress("LiftReturnOrAssignment") | |
if (idle) { | |
if (wasNotIdle) { | |
// Notify observers to avoid Espresso race detector. | |
idlingCallbacks.forEach { it.onTransitionToIdle() } | |
} | |
wasNotIdle = false | |
} else { | |
wasNotIdle = true | |
// Check next frame. | |
activity.findViewById<View>(android.R.id.content).postDelayed({ | |
isIdleNow | |
}, 16) | |
} | |
return idle | |
} | |
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) { | |
idlingCallbacks.add(callback) | |
} | |
/** | |
* Find all binding classes in all currently available fragments. | |
*/ | |
private fun getBindings(): List<ViewDataBinding> { | |
val fragments = (activity as? FragmentActivity) | |
?.supportFragmentManager | |
?.fragments | |
val bindings = | |
fragments?.mapNotNull { | |
it.view?.getBinding() | |
} ?: emptyList() | |
val childrenBindings = fragments?.flatMap { it.childFragmentManager.fragments } | |
?.mapNotNull { it.view?.getBinding() } ?: emptyList() | |
return bindings + childrenBindings | |
} | |
} | |
private fun View.getBinding(): ViewDataBinding? = DataBindingUtil.getBinding(this) | |
/** | |
* Sets the activity from an [ActivityScenario] to be used from [DataBindingIdlingResource]. | |
*/ | |
fun DataBindingIdlingResource.monitorActivity( | |
activityScenario: ActivityScenario<out FragmentActivity> | |
) { | |
activityScenario.onActivity { | |
this.activity = it | |
} | |
} | |
/** | |
* Sets the fragment from a [FragmentScenario] to be used from [DataBindingIdlingResource]. | |
*/ | |
fun DataBindingIdlingResource.monitorFragment(fragmentScenario: FragmentScenario<out Fragment>) { | |
fragmentScenario.onFragment { | |
this.activity = it.requireActivity() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.test.espresso.idling.CountingIdlingResource | |
// Espresso idling resources are a synchronization mechanism for Espresso and your long running operations. | |
object EspressoIdlingResource { | |
private const val RESOURCE = "GLOBAL" | |
// allows you to increment or decrement the counter | |
// counter >= 0, the app is working | |
// counter < 0, the app is idle | |
@JvmField | |
val countingIdlingResource = CountingIdlingResource(RESOURCE) | |
fun increment() { | |
countingIdlingResource.increment() | |
} | |
fun decrement() { | |
if (!countingIdlingResource.isIdleNow) { | |
countingIdlingResource.decrement() | |
} | |
} | |
} | |
inline fun <T> wrapEspressoIdlingResource(function: () -> T): T { | |
// Espresso does not work well with coroutines yet. See | |
// https://github.com/Kotlin/kotlinx.coroutines/issues/982 | |
EspressoIdlingResource.increment() // Set app as busy. | |
return try { | |
function() | |
} finally { | |
EspressoIdlingResource.decrement() // Set app as idle. | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.test.core.app.ActivityScenario | |
import androidx.test.core.app.ApplicationProvider.getApplicationContext | |
import androidx.test.espresso.Espresso.onView | |
import androidx.test.espresso.IdlingRegistry | |
import androidx.test.espresso.action.ViewActions.click | |
import androidx.test.espresso.action.ViewActions.replaceText | |
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist | |
import androidx.test.espresso.assertion.ViewAssertions.matches | |
import androidx.test.espresso.matcher.ViewMatchers.* | |
import androidx.test.ext.junit.runners.AndroidJUnit4 | |
import androidx.test.filters.LargeTest | |
import com.example.android.architecture.blueprints.todoapp.util.DataBindingIdlingResource | |
import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingResource | |
import kotlinx.coroutines.runBlocking | |
import org.hamcrest.CoreMatchers.not | |
import org.junit.After | |
import org.junit.Before | |
import org.junit.Test | |
import org.junit.runner.RunWith | |
@RunWith(AndroidJUnit4::class) | |
@LargeTest | |
class MyActivityTest { | |
// An idling resource that waits for Data Binding to have no pending bindings. | |
private val dataBindingIdlingResource = DataBindingIdlingResource() | |
/** | |
* Idling resources tell Espresso that the app is idle or busy. This is needed when operations | |
* are not scheduled in the main Looper (for example when executed on a different thread). | |
* In other words, Expresso will wait when the app is busy (the counter is greater than 0) | |
*/ | |
@Before | |
fun registerIdlingResource() { | |
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource) | |
IdlingRegistry.getInstance().register(dataBindingIdlingResource) | |
} | |
/** | |
* Unregister your Idling Resource so it can be garbage collected and does not leak any memory. | |
*/ | |
@After | |
fun unregisterIdlingResource() { | |
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource) | |
IdlingRegistry.getInstance().unregister(dataBindingIdlingResource) | |
} | |
@Test | |
fun Test1() { | |
// Set initial state. | |
// repository code ... | |
// Start up Tasks screen. | |
val activityScenario = ActivityScenario.launch(MyActivity::class.java) | |
dataBindingIdlingResource.monitorActivity(activityScenario) // LOOK HERE | |
// Espresso code will go here. | |
// Verify the UI result | |
// .... | |
// Make sure the activity is closed before resetting the db: | |
activityScenario.close() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment