Espresso wait for network call

19 February 2020 stoefln Leave a comment espresso

By default you don’t need to take care to handle waiting for activities.

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. This is because it is not aware of other threads executing a network call. You probably know that 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 which will provide 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).
Be aware that 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 iding resources as we outlined in another post.

// 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)

    }
}

 

Using the idling resource inside your test:

The missing piece is to register the idling resource with the espresso test runner, so it 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: , ,

Like this article? there’s more where that came from.

Leave a Reply

Your email address will not be published. Required fields are marked *