Member preview

Instrumental Test: Better Espresso without sleep

Sometimes due to network fetching, we need to add Thread.sleep(..) or SystemClock.sleep(...) so that our instrumental test could be run and pass. This blog will show you how to eliminate the need of sleep in your instrumental test.

Example App to Test

Assuming we have an app the perform a network fetch as below.

You could get the above example app from

Instrumental test

To create an instrumental test for it, just perform the below steps

  1. Key in the word (e.g. Trump)
  2. Click CHECK SEARCH COUNT button
  3. Check that the result found will be shown
@RunWith(AndroidJUnit4::class)
class InstrumentedTest {
@get:Rule
private val activityRule = ActivityTestRule(
MainActivity::class.java, false, false)

@Before
fun setup() {
activityRule.launchActivity(null)
}

@Test
@Throws(Exception::class)
fun launchAndSearch() {
Espresso.onView(ViewMatchers.withId(R.id.edit_search))
.perform(ViewActions.replaceText("Trump"))
Espresso.onView(ViewMatchers.withId(R.id.btn_search))
.perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.txt_search_result))
.check(ViewAssertions.matches(
ViewMatchers.withText("16774 result found")))
}
}

However, the test will fail. Between step 2 and and step 3, a network fetched is done asynchronously. It fails because the check for Text is done before the network fetch is completed.

To handle this issue, the usual thought is, let’s add a sleep to wait for the process as below. This is feasible, and the test should pass if we wait long enough for the result to show.

Espresso.onView(ViewMatchers.withId(R.id.edit_search))
.perform(ViewActions.replaceText("Trump"))
Espresso.onView(ViewMatchers.withId(R.id.btn_search))
.perform(ViewActions.click())
Thread.sleep(5000)
Espresso.onView(ViewMatchers.withId(R.id.txt_search_result))
.check(ViewAssertions.matches(
ViewMatchers.withText("16774 result found")))

Sleep is not good

However, sleep is not ideal, as anything that depends on timer is bounce to have it’s issue especially in a test environment. e.g.

  • If time need to fetch the network is much faster, then we wasted the additional time waiting
  • If a network so happens to be slow and get greater than the given time, then the test will fail prematurely.

The solution: IdlingResource

Android espresso library does provide a class that helps to pause the test until the resource is idle. It is called IdlingResource

With IdlingResource, my test would look like below, no more sleep 👍

@Test
@Throws(Exception::class)
fun launchAndSearch() {
Espresso.onView(ViewMatchers.withId(R.id.edit_search))
.perform(ViewActions.replaceText("Trump"))
Espresso.onView(ViewMatchers.withId(R.id.btn_search))
.perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.txt_search_result))
.check(ViewAssertions.matches(
ViewMatchers.withText("16774 result found")))
}

You could read about it in below.

I’ll show you how it’s I use IdlingResource for my example above.

How to get IdlingResource work?

What I did is, I create a class of FetcherIdlingResouce which is an IdlingResource interface, and FetcherListener interface

class FetchingIdlingResource: IdlingResource, FetcherListener {
private var idle = true
private var resourceCallback:
IdlingResource.ResourceCallback? = null

override fun getName(): String {
return FetchingIdlingResource::class.java.simpleName
}

override fun isIdleNow() = idle

override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback?) {
resourceCallback = callback
}

override fun doneFetching() {
idle = true
resourceCallback?.onTransitionToIdle()
}

override fun beginFetching() {
idle = false
}
}

IdlingResource Interface

With that, now let’s implement all the needed interfaces

override fun getName(): String {
return FetchingIdlingResource::class.java.simpleName
}

override fun isIdleNow() = idle

override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback?) {
resourceCallback = callback
}

The name is required for the internal register to identify this resource against others.

The isIdleNow is used to check and and confirm that the resource is ready i.e. idle = true, and the test could continue.

The resourceCallback is provided to inform it is ready to be transitioning to idle state.

FetcherListener interface

This interface is our project own listener that will listener to the network fetcher begin or end, and

interface FetcherListener {
fun doneFetching()
fun beginFetching()
}

Hence within our API fetching mechanism, when I subscribe to fetch the network call, I’ll call beginFetching(), and upon completing, I’ll call doneFetching().

private fun beginSearch(searchString: String) {
disposable = wikiApiServe.hitCountCheck(...)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { fetcherListener?.beginFetching() }
.doFinally { fetcherListener?.doneFetching() }
.subscribe(
{ result -> doSomethingWithResult() },
{ error -> doSomethingWithError() }
)
}

Connecting to the test

Lastly, I’ll just to have connect these tracking of Fetching begin and end to our test.

@get:Rule
private val activityRule = ActivityTestRule(
MainActivity::class.java, false, false)
private val fetchingIdlingResource = FetchingIdlingResource()

@Before
fun setup() {
activityRule.launchActivity(null)
IdlingRegistry.getInstance().register(fetchingIdlingResource)
activityRule.activity.setFetcherListener(fetchingIdlingResource)

}

I just need to register the fetchingIdlingResource to the IdlingRegistry. This will let the test to always observe that this IdlingResource is idle before proceeding.

Since I need the activity to tell that fetchingIdlingResource the network fetching status, hence I send it as fetcherListener to the activity.

That’s it. All done!! No more sleep needed

What’s next?

As we’re now having the ability to not sleep, but we have another problem. The problem is if we check based on the network we fetch, the result would change from time to time. Beside it would be relying on network availability to work. This is not ideal for test.

We need need a way to record our fetch result, and replay it. How to do that? Check out the next blog below…

p/s: You could get the code of this project is in the next blog, for the complete improvement of Espresso test.

Additional Finding

Jake Wharton has written a simple one class for Okhttp3 support for IdlingResource. So you could just use it, without writing your own.

Note: I could use it in this wikiApi App, but not in my protobuf app, as upon parsing the profobut data, it is too slow, and verification has completed. Hence in that case, I need my own IdlingResource.

I hope this post is helpful to you. You could check out my other interesting topics here.

Follow me on medium, Twitter or Facebook for little tips and learning on Android, Kotlin etc related topics. ~Elye~