© Denys Zelenchuk 2019
Denys ZelenchukAndroid Espresso Revealedhttps://doi.org/10.1007/978-1-4842-4315-2_4

4. Handling Network Operations and Asynchronous Actions

Denys Zelenchuk1 
(1)
Zürich, Switzerland
 

One of the key benefits of the Espresso framework is its test robustness. It is achieved through automatic synchronization of most of the test actions. Espresso waits for the main application UI thread while it is busy and releases test actions after the UI thread becomes idle. Moreover, it also waits for AsyncTask operations to complete before it moves to the next test step. In this chapter, we will see how Espresso can handle network operations using the IdlingResource mechanism and become familiar with the ConditionWatcher mechanism as an alternative to IdlingResource.

IdlingResource Basics

Each time your test invokes onView() or onData(), Espresso waits to perform the corresponding UI action or assertion until the following synchronization conditions are met:
  • The message queue is empty.

  • There are no instances of AsyncTask currently executing a task.

  • All developer-defined idling resources are idle.

By performing these checks, Espresso substantially increases the likelihood that only one UI action or assertion can occur at any given time. This capability gives you more reliable and dependable test results.

However, it is not possible in every case to rely on automatic synchronization, for instance when the application being tested executes network calls via ThreadPoolExecutor. In order to let Espresso handle these kinds of long-lasting asynchronous operations, the IdlingResource must be created and registered before the test is executed.

It is important to register IdlingResource when these operations update the application UI you would like to further validate.

The common use cases in which IdlingResource can be used are when your app is:
  • Performing network calls.

  • Establishing database connections.

At the moment, Espresso provides the following idling resources:
  • CountingIdlingResource —Maintains a counter of active tasks. When the counter is zero, the associated resource is considered idle. This functionality closely resembles that of a semaphore. In most cases, this implementation is sufficient for managing your app’s asynchronous work during testing.

  • UriIdlingResource —Similar to CountingIdlingResource, but the counter needs to be zero for a specific period of time before the resource is considered idle. This additional waiting period takes consecutive network requests into account, where an app in your thread might make a new request immediately after receiving a response to a previous request.

  • IdlingThreadPoolExecutor —A custom implementation of ThreadPoolExecutor that keeps track of the total number of running tasks within the created thread pools. This class uses a CountingIdlingResource to maintain the counter of active tasks.

  • IdlingScheduledThreadPoolExecutor—A custom implementation of ScheduledThreadPoolExecutor . It provides the same functionality and capabilities as the IdlingThreadPoolExecutor class, but it can also keep track of tasks that are scheduled for the future or are scheduled to execute periodically.

To start using an idling resource mechanism in an application, the following dependency must be added to the application buid.gradle file (dependencies are mentioned for the Android Support and AndroidX Libraries).

IdlingResource Dependency in the Android Support Library.
androidTestImplementation "com.android.support.test.espresso.idling:idling-concurrent:3.0.1"
IdlingResource Dependency in the AndroidX Library.
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0'

These idling resource types use CountingIdlingResource in their implementation, so we will focus on CountingIdlingResource as a reference.

The IdlingResource interface contains three methods:
  • getName()—Returns the name of the resources.

Note

The IdlingResource name is represented by a String class and is used when logging, and for registration/unregistration purposes. Therefore, the name of the resource should be unique.

  • isIdleNow()—Returns true if the resource is currently idle. Espresso will always call this method from the main thread; therefore, it should be non-blocking and return immediately.

  • registerIdleTransitionCallback()—Registers the given resource callback with the idling resource. The registered callback is then used in the isIdleNow() method.

Note

The IdlingResource class contains a ResourceCallback interface that is used in the registerTransitionCallback() method. Whenever the application is going to switch states from busy to idle, the callback.onTransitionToIdle() method should be called to notify Espresso about it.

CountingIdlingResource is an implementation of IdlingResource that determines idleness by maintaining an internal counter. When the counter is zero, it is considered to be idle; when it is non-zero, it is not idle. This is very similar to the way a java.util.concurrent.Semaphore behaves.

The counter may be incremented or decremented from any thread. If it reaches an illogical state (like a counter that’s less than zero), it will throw an IllegalStateException. This class can then be used to wrap operations that, while in progress, block tests from accessing the UI.

Writing the Code

This is how the simple CountingIdlingResource looks in our application (see the util/SimpleCountingIdlingResource.java file from the main application source code):
public final class SimpleCountingIdlingResource implements IdlingResource {
    private final String mResourceName;
    private final AtomicInteger counter = new AtomicInteger(0);
    // written from main thread, read from any thread.
    private volatile ResourceCallback resourceCallback;
    /**
     * Creates a SimpleCountingIdlingResource
     *
     * @param resourceName the name of the resource to report to Espresso.
     */
    public SimpleCountingIdlingResource(String resourceName) {
        mResourceName = checkNotNull(resourceName);
    }
    @Override
    public String getName() {
        return mResourceName;
    }
    @Override
    public boolean isIdleNow() {
        return counter.get() == 0;
    }
    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
    /**
     * Increments the count of in-flight transactions to the resource being monitored.
     */
    public void increment() {
        counter.getAndIncrement();
    }
    /**
     * Decrements the count of in-flight transactions to the resource being monitored.
     *
     * If this operation results in the counter falling below 0 - an exception is raised.
     *
     * @throws IllegalStateException if the counter is below 0.
     */
    public void decrement() {
        int counterVal = counter.decrementAndGet();
        if (counterVal == 0) {
            // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
            if (null != resourceCallback) {
                resourceCallback.onTransitionToIdle();
            }
        }
        if (counterVal < 0) {
            throw new IllegalArgumentException("Counter has been corrupted!");
        }
    }
}
The SimpleCountingIdlingResource class is used by the EspressoIdlingResource class in the same location that contains a static reference to it (see the util/EspressoIdlingResource.java file) and it uses its increment() and decrement() methods:
public class EspressoIdlingResource {
    private static final String RESOURCE = "GLOBAL";
    private static SimpleCountingIdlingResource mCountingIdlingResource =
            new SimpleCountingIdlingResource(RESOURCE);
    public static void increment() {
        mCountingIdlingResource.increment();
    }
    public static void decrement() {
        mCountingIdlingResource.decrement();
    }
    public static IdlingResource getIdlingResource() {
        return mCountingIdlingResource;
    }
}
Now let’s take a look at the tasks/TasksPresenter.java class from the main application source code where EspressoIdlingResource is used. This class is responsible for loading TO-DOs and presenting them in the TO-DO list. You can see how the EspressoIdlingResource.increment() method is called when the task load process starts to pause the tests. When the task is loaded, EspressoIdlingResource.decrement() is called to notify Espresso about the upcoming idling state:
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
    if (showLoadingUI) {
        mTasksView.setLoadingIndicator(true);
    }
    if (forceUpdate) {
        mTasksRepository.refreshTasks();
    }
    // The network request might be handled in a different thread so make sure Espresso
    // knows that the app is busy until the response is handled.
    EspressoIdlingResource.increment(); // App is busy until further notice
    mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
          @Override
          public void onTasksLoaded(List<Task> tasks) {
              List<Task> tasksToShow = new ArrayList<Task>();
              // This callback may be called twice, once for the cache and once for loading
              // the data from the server API, so we check before decrementing, otherwise
              // it throws "Counter has been corrupted!" exception.
              if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
                  EspressoIdlingResource.decrement(); // Set app as idle.
              }
          ... // other code here
          }
    }
}

Running the First Test

To see EspressoIdlingResource in action, we add some logging to the increment() and decrement() methods in the SimpleCountingIdlingResource.java class and run the addNewToDosChained() test:
@Override
public boolean isIdleNow() {
    Log.d(getName(), "Counter value is " + counter.get());
    return counter.get() == 0;
}
and
public void decrement() {
    int counterVal = counter.decrementAndGet();
    Log.d(getName(), "Counter decremented. Value is " + counterVal);
    if (counterVal == 0) {
        // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
        if (null != resourceCallback) {
            resourceCallback.onTransitionToIdle();
        }
    }
    if (counterVal < 0) {
        throw new IllegalArgumentException("Counter has been corrupted!");
    }
}
During the test run, observe the logcat logs of our application, which can be filtered out by the GLOBAL tag. Figure 4-1 shows what you will see; each time a TO-DO is added, a TO-DO list is displayed to the user and the counter is incremented and decremented just after the load is done.
../images/469090_1_En_4_Chapter/469090_1_En_4_Fig1_HTML.jpg
Figure 4-1

Idling resource counter logging

IdlingResource should be registered before usage. IdlingRegistry handles registering and unregistering IdlingResource.

Registering and Unregistering IdlingResource Instances.
@Before
fun registerResources() {
    val idlingRegistry = IdlingRegistry.getInstance()
    val okHttp3IdlingResource = OkHttp3IdlingResource(client)
    val picassoIdlingResource = PicassoIdlingResource()
    idlingRegistry.register(okHttp3IdlingResource)
    idlingRegistry.register(picassoIdlingResource)
}
@After
fun unregisterResources() {
    val idlingRegistry = IdlingRegistry.getInstance()
    for (idlingResource in idlingRegistry.resources) {
        if (idlingResource == null) {
            continue
        }
        idlingRegistry.unregister(idlingResource)
    }
}

So, at this moment the CountingIdlingResource mechanism should be clear. This example described the way that we handle long-lasting or asynchronous actions of the application being tested. It is important to be careful with such idling resources and not to lock them during the test execution.

OkHttp3IdlingResource

Another idling resource sample that we look at is the OkHttp3IdlingResource . Why we should specifically look at it? OkHttp is one of the most used HTTP client libraries. It was developed by Square and used in a lot of Android applications. Probably because of this one, Square developer Jake Wharton implemented and open sourced this resource. See https://github.com/JakeWharton/okhttp-idling-resource . Here is how it looks.

chapter4.idlingresources.OkHttp3IdlingResource.kt.
public final class OkHttp3IdlingResource implements IdlingResource {
    @CheckResult
    @NonNull
    @SuppressWarnings("ConstantConditions") // Extra guards as a library.
    public static OkHttp3IdlingResource create(@NonNull String name, @NonNull OkHttpClient client) {
        if (name == null) throw new NullPointerException("name == null");
        if (client == null) throw new NullPointerException("client == null");
        return new OkHttp3IdlingResource(name, client.dispatcher());
    }
    private final String name;
    private final Dispatcher dispatcher;
    volatile ResourceCallback callback;
    private OkHttp3IdlingResource(String name, Dispatcher dispatcher) {
        this.name = name;
        this.dispatcher = dispatcher;
        dispatcher.setIdleCallback(new Runnable() {
            @Override public void run() {
                ResourceCallback callback = OkHttp3IdlingResource.this.callback;
                if (callback != null) {
                    callback.onTransitionToIdle();
                }
            }
        });
    }
    @Override public String getName() {
        return name;
    }
    @Override public boolean isIdleNow() {
        return dispatcher.runningCallsCount() == 0;
    }
    @Override public void registerIdleTransitionCallback(ResourceCallback callback) {
        this.callback = callback;
    }
}
Basically, this resource works out-of-the-box and almost everything is done for us here. The dispatcher.runningCallsCount() method call from the iSIdleNow() method returns both running synchronous and asynchronous requests counts, which are compared to zero. When the result is true, the resource is idle. There are, however, some steps we still have to take in order to use it:
  1. 1.

    Add a dependency in the build.gradle file:

     
androidTestCompile 'com.jakewharton.espresso:okhttp3-idling-resource:1.0.0'
  1. 2.

    In your test code, obtain the OkHttpClient instance and create an idling resource:

     
OkHttpClient client = // ... get OkHttpClient instance
IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client);
  1. 3.

    Register the idling resource in the test code before running any Espresso tests:

     
IdlingRegistry.getInstance().register(resource);

By the way, don’t use the deprecated Espresso.registerIdlingResources() method ; instead use the IdlingRegistry implementation shown in this section.

Picasso IdlingResource

Picasso is a powerful image-downloading and caching library for Android from Square. Picasso allows for hassle-free image loading in your application—often in one line of code ( http://square.github.io/picasso/ ):
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

Picasso is the most popular image-downloading library for Android, which means it is a perfect candidate for another type of IdlingResource. The image-download idling resource can be used when we want to ensure that the whole application window layout is loaded together with the graphics. This can be extremely important in cases where graphical resources should be verified in tests. Here is the example of the PicassoIdling resource that’s also implemented in the androidTest/com.squareup.picasso package.

androidTest/com.squareup.picasso.PicassoIdlingResource.java.
public class PicassoIdlingResource implements IdlingResource, ActivityLifecycleCallback {
    private static final int IDLE_POLL_DELAY_MILLIS = 100;
    private ResourceCallback mCallback;
    private WeakReference<Picasso> mPicassoWeakReference;
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    @Override
    public String getName() {
        return "PicassoIdlingResource";
    }
    @Override
    public boolean isIdleNow() {
        if (isIdle()) {
            notifyDone();
            return true;
        } else {
      /* Force a re-check of the idle state in a little while.
       * If isIdleNow() returns false, Espresso only polls it every few seconds which can slow down our tests.
       */
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    isIdleNow();
                }
            }, IDLE_POLL_DELAY_MILLIS);
            return false;
        }
    }
    public boolean isIdle() {
        return mPicassoWeakReference == null
                || mPicassoWeakReference.get() == null
                || mPicassoWeakReference.get().targetToAction.isEmpty();
    }
    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        mCallback = resourceCallback;
    }
    void notifyDone() {
        if (mCallback != null) {
            mCallback.onTransitionToIdle();
        }
    }
    @Override
    public void onActivityLifecycleChanged(Activity activity, Stage stage) {
        switch (stage) {
            case RESUMED:
                mPicassoWeakReference = new WeakReference<>(Picasso.with(activity));
                break;
            case PAUSED:
                // Clean up reference
                mPicassoWeakReference = null;
                break;
            default: // NOP
        }
    }
}

Note

The reason that the Picasso IdlingResource is in a separate package is because of the visibility of the targetToAction variable in the Picasso class, which is package protected.

ConditionWatcher as an Alternative to IdlingResource

As you may notice, the IdlingResource implementation is not trivial and requires continuous control over registering and unregistering. It is also not convenient to use IdlingResource in deep UI tests when a specific activity instance is needed to make it work.

As an alternative, you can try the ConditionWatcher class from AzimoLabs ( https://github.com/AzimoLabs/ConditionWatcher ). It is simple class that makes Android automation testing easier, faster, cleaner, and more intuitive. It synchronizes operations that might occur on any thread, with the test thread. ConditionWatcher can be used as a replacement to Espresso’s IdlingResources or it can work in parallel with them.

This is how it works: ConditionWatcher receives an instance of the Instruction class that contains a logical expression. Tests are paused until the moment the condition returns true. After that, the tests are immediately released. If the condition is not met within a specified timeout, the exception will be thrown and the test will fail.

ConditionWatcher acts on the same thread it is requested, which is the test thread. By default, ConditionWatcher includes three methods:
  • setWatchInterval()  — Sets the interval for periodic check of the logical expression. By default, it is set to 250 milliseconds.

  • setTimeoutLimit()  — Sets the timeout for the ConditionWatcher to wait for a true value from the checkCondition() method. By default, it is set to 60 seconds.

  • waitForCondition()  — Takes instructions containing a logical expression as a parameter and calls its checkCondition() method with the currently set interval, until it returns value true or until the timeout is reached. During that time, the test code won’t proceed to the next line. If timeout is reached, an Exception is thrown.

From the other side, the Instruction class happens to have a very similar structure to IdlingResource:
  • checkCondition() — A core method that’s equivalent to isIdleNow() of IdlingResource. It’s a logical expression and its changes, along with the monitored dynamic resource status, should be implemented there.

  • getDescription() — A string returned along with the timeout exception. The test author can include helpful information for the test crash debugging process.

  • setDataContainer() and getDataContainer() —A bundle that can be added to the Instruction class to share primitive types (e.g., a universal instruction that waits for any kind of view to become visible can be created, and resId could be sent via the bundle).

The following dependency should be added to the build.gradle file in order to start using ConditionWatcher:
dependencies {
    androidTestCompile 'com.azimolabs.conditionwatcher:conditionwatcher:0.2'
}

Or just copy the source code of the two ConditionWatcher.java and Instruction.java classes into your test source code.

The simplest example of ConditionWatcher usage is a condition to wait for an element be displayed on the screen:
ConditionWatcher.waitForCondition(new Instruction() {
    @Override
    public String getDescription() {
        return "waitForElementIsDisplayed";
    }
    @Override
    public boolean checkCondition() {
        try {
            interaction.check(matches(isDisplayed()));
            return true;
        } catch (NoMatchingViewException ex) {
            return false;
        }
    }
});
I prefer to wrap the ConditionWatcher into a method instead of creating a class that extends the Instruction class. Next, you see an example of the waitForElementIsDisplayed(final ViewInteraction interaction, final int timeout) watcher from the ConditionWatchers.java class:
public static ViewInteraction waitForElementIsDisplayed(
        final ViewInteraction interaction,
        final int timeout) throws Exception {
    ConditionWatcher.setTimeoutLimit(timeout);
    ConditionWatcher.waitForCondition(new Instruction() {
        @Override
        public String getDescription() {
            return "waitForElementIsDisplayed";
        }
        @Override
        public boolean checkCondition() {
            try {
                interaction.check(matches(isDisplayed()));
                return true;
            } catch (NoMatchingViewException ex) {
                return false;
            }
        }
    });
    return interaction;
}
With this implementation of waitForElementIsDisplayed(), we receive one important benefit—if the watcher receives ViewInteraction as a parameter, the wrapper method can return the same ViewInteraction, which simplifies our test source code:
private ViewInteraction addTaskFab = onView(withId(R.id.fab_add_task));
@Test
public void waitForElementCondition() throws Exception {
    waitForElementIsDisplayed(addTaskFab, 4000).perform(click());
}

Now let’s move to more complicated examples. In our sample application, we have a nasty snackbar that pops up every time a new TO-DO is added. It doesn’t allow us to add multiple TO-DOs to our list without waiting until it disappears. Our task is to create a watcher that will wait for the snackbar view to be gone. This is how it can be done.

chapter4.conditionwatchers.ConditionWatchers.tasksListSnackbarGone().
public static void tasksListSnackbarGone() throws Exception {
    ConditionWatcher.waitForCondition(new Instruction() {
        @Override
        public String getDescription() {
            return "Condition tasksListSnackbarGone";
        }
        @Override
        public boolean checkCondition() {
            final FragmentActivity fragmentActivity = getCurrentActivity();
            if (fragmentActivity != null) {
                Fragment currentFragment = fragmentActivity
                        .getSupportFragmentManager()
                        .findFragmentById(R.id.contentFrame);
                if (currentFragment instanceof TasksFragment) {
                    View contentView =
                    fragmentActivity.getWindow().getDecorView().findViewById(android.R.id.content);
                    if (contentView != null) {
                        TextView snackBarTextView =
                             contentView.findViewById(android.support.design.R.id.snackbar_text);
                        return snackBarTextView == null;
                    }
                }
            }
            return false;
        }
    });
}

ConditionWatchers can be extremely helpful when we have to wait for the different view states, but we should not overuse them in terms of waiting time. A problem can occur in cases when we may wait too much for a specific state of the view to be reached. When this waiting time becomes too long, it can seem like an issue with the application being tested and it is better to raise a bug than handle it inside your tests. Ideally, in most situations, IdlingResources should handle the majority of time the application is not idle, so ConditionWatchers should be a small addition to the waiting mechanism and be used occasionally, like in our snackbar case.

Exercise 12

Using a ConditionWatcher in a Test
  1. 1.

    Implement a test that opens the menu drawer and navigates to another section. In this test, add a condition watcher that waits for a menu drawer to be shown or hidden. Use ViewMatchers.isDisplayed() for the shown state and hamcrest CoreMatchers.not(ViewMatchers.isDisplayed()) for hidden.

     
  2. 2.

    Implement a waitForElement() ConditionWatcher that can be used with the DataInteraction type. Use the ViewInteraction waitForElement() function as a reference.

     

Making Condition Watchers Part of Espresso Kotlin DSL

Chapter 3 explained the Espresso Kotlin DSL as an example of much cleaner and compact test code. As you may notice, in the current implementation, all the functions from the ConditionWatchers class are not yet ready to be used in a similar way.

The thing is ConditionWatchers, as well as other Espresso methods, are implemented and executed in the same place and at the same time as the test code, which is the opposite to how IdlingResources are used—by registering them before the test run (usually in the @Before method).

So, ConditionWatchers should ideally become part of the Espresso Kotlin DSL and be used as one of the chains while writing the test code. This is how our ConditionWatchers can be declared as part of the DSL (see EspressoDsl.kt for the implementation details):
  • ConditionWatchers.waitForElement():

fun ViewInteraction.wait(): ViewInteraction =
        ConditionWatchers.waitForElement(this, FOUR_SECONDS)
  • ConditionWatchers.waitForElementFullyVisible():

fun ViewInteraction.waitFullyVisible(): ViewInteraction =
        ConditionWatchers.waitForElementFullyVisible(this, FOUR_SECONDS)
  • ConditionWatchers.waitForElementIsGone():

fun ViewInteraction.waitForGone(): ViewInteraction =
        ConditionWatchers.waitForElementIsGone(this, FOUR_SECONDS)

All of these examples have the ViewInteraction return type and can be chained to Espresso test code as follows.

chapter3.testsamples.ViewActionsKotlinDslTest.addsNewToDoWithWaiterDsl().
@Test
fun addsNewToDoWithWaiterDsl() {
    // adding new TO-DO
    addFab.click()
    taskTitleField.wait().type(toDoTitle).closeKeyboard()
    taskDescriptionField.type(toDoDescription).closeKeyboard()
    editDoneFab.click()
    snackbar.waitForGone()
    // verifying new TO-DO with title is shown in the TO-DO list
    viewWithText(toDoTitle).checkDisplayed()
}

Exercise 13

ConditionWatcher as Part of the DSL
  1. 1.

    Implement a test that opens a menu drawer and navigates to another section. In this test, add a condition watcher that waits for the menu drawer to be shown or hidden. Use ViewMatchers.isDisplayed() for the shown state and hamcrest CoreMatchers.not(ViewMatchers.isDisplayed()) for hidden.

     
  2. 2.

    Make the DataInteraction waitForElement() function in the previous task part of the DSL.

     

Summary

Properly handling network operations and asynchronous actions is a must-have in your UI tests. Applying IdlingResource or ConditionWatcher makes your UI tests much more stable and reliable. After using them at least once, it will be clear that there is no need to use explicit Thread.sleep() methods all over the tests, which is a bad practice and error-prone.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.179.220