19 February 2020 Leave a comment Espresso
By default you don’t need to take care to handle waiting for activities. But there’s an exception which can make Espresso tests fail. In this article you learn why this happens and how you can handle it.
An example of Espresso wait for network call
Let’s consider this example:
// MainActivity.kt:
...
start_activity_button.setOnClickListener {
val intent = Intent(context, LoginActivity::class.java)
Thread.sleep(2000)
startActivity(intent)
}
This testing code will run just fine:
// LoginTest.kt:
...
@Rule @JvmField
val activityRule: ActivityTestRule = ActivityTestRule(MainActivity::class.java,true, false)
@Test
fun shouldLoginUser(){
activityRule.launchActivity(Intent())
onView(withId(R.id.start_activity_button)).perform(click())
onView(withId(R.id.et_email)).perform(typeText("mytestemail@email.com")).perform(closeSoftKeyboard())
onView(withId(R.id.et_password)).perform(typeText("12345678")).perform(closeSoftKeyboard())
onView(withId(R.id.bt_login)).perform(click())
Intents.intended(IntentMatchers.hasComponent(MainActivity::class.java!!.getName()))
}
Espresso will know that it needs to wait for the UI thread to get idle BEFORE looking up the R.id.et_email view. Thread.sleep does not change that, since it keeps the current thread (the UI thread) busy.
However, let’s say we would need to make network call before opening the LoginActivity:
// MainActivity.kt:
...
start_activity_button.setOnClickListener {
val intent = Intent(context, LoginActivity::class.java)
// using kotlin coroutines to simulate waiting for a network request on a different thread:
val job = GlobalScope.launch {
delay(2000)
}
job.invokeOnCompletion {
startActivity(intent)
}
}
This would cause Espresso to fail with an exception:
E/TestRunner: androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: my.app.com:id/et_email
The problem with Espresso and network calls
Espresso can’t handle network calls automatically because it is not aware of other threads executing a network call. The default way to fix this is to use an IdlingResource for that. In this particular case we could use the android counting idling resource implementation to solve the issue. First import following gradle dependency:
// Espresso
def androidx_test_espresso = “3.1.0”
androidTestImplementation “androidx.test.espresso:espresso-core:$androidx_test_espresso”
androidTestImplementation “androidx.test.espresso:espresso-contrib:$androidx_test_espresso”
implementation “androidx.test.espresso:espresso-idling-resource:$androidx_test_espresso”
androidTestImplementation “androidx.test.espresso:espresso-intents:$androidx_test_espresso”// androidx.test
def androidx_test = “1.1.0”
androidTestImplementation “androidx.test:runner:$androidx_test”
androidTestImplementation “androidx.test:core:$androidx_test”
androidTestImplementation “androidx.test.ext:junit-ktx:$androidx_test”
Now create a class that provides your countingIdlingResource to the rest of your application:
// CountingIdlingResourceSingleton.kt:
import androidx.test.espresso.idling.CountingIdlingResource
object CountingIdlingResourceSingleton {
private const val RESOURCE = "GLOBAL"
@JvmField val countingIdlingResource = CountingIdlingResource(RESOURCE)
fun increment() {
countingIdlingResource.increment()
}
fun decrement() {
if (!countingIdlingResource.isIdleNow) {
countingIdlingResource.decrement()
}
}
}
This class will hold the information about whether your application is still busy (and espresso needs to keep waiting) or is idle (and espresso can continue with executing the next step).
Heads up: since we need to access this class from within our application code, it needs to be placed inside of your src/main/java directory and NOT inside of src/androidTest/java. This is one of the disadvantages of idling resources.
// MainActivity.kt:
start_activity_button.setOnClickListener {
val intent = Intent(context, LoginActivity::class.java)
CountingIdlingResourceSingleton.increment()
// again we use a kotlin coroutine to simulate a 3 second network request:
val job = GlobalScope.launch {
// our network call starts
delay(3000)
}
job.invokeOnCompletion {
// our network call ended!
CountingIdlingResourceSingleton.decrement()
startActivity(intent)
}
}
How to use the idling resource inside your test
The missing piece is to register the idling resource with the espresso test runner. This can observe our count variable and wait accordingly during execution of our test:
// LoginTest.kt:
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance().register(CountingIdlingResourceSingleton.countingIdlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(CountingIdlingResourceSingleton.countingIdlingResource)
}
Tags: android ui testing, espresso, espresso idling resource
Like this article? there’s more where that came from.
- Testproject.io retired. What alternatives are there?
- Testing with Espresso: use, challenges, and alternatives
- Manual Testing vs Automation Testing: what is better and when to use it?
- How to solve “tool ‘xcodebuild’ requires Xcode, but active developer directory ‘/Library/Developer/CommandLineTools’ is a command line tools instance”
- How to report code coverage data from Flutter tests?