13

I have an activity which is using a postDelayed call:

public class SplashActivity extends Activity {
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(...);
        handler.postDelayed(new Runnable() { 
            public void run() { finish(); }
        }, 3000L);
    }
 }

This runs at app startup, and i need to navigate it and my login screen. However, the UIController's loopMainThreadUntilIdle doesn't seem to take the underlying MessageQueue in the handler into account. As such, this action finishes immediately while there is still messages in the queue.

onView(withId(R.id.splash_screen)).perform(new ViewAction() {
    @Override
    public Matcher<View> getConstraints() {
        return isAssignableFrom(View.class);
    }

    @Override
    public String getDescription() {
        return "";
    }

    @Override
    public void perform(final UiController uiController, final View view) {
        uiController.loopMainThreadUntilIdle();
    }
});

I've been unable to figure out how to block until the queue is drained. Android itself is preventing me from doing a lot of things i would have tried (like extending Handler and overriding the postDelayed method, etc...)

Anyone have any suggestions on how to handle postDelayed?

I'd rather avoid uiController.loopMainThreadForAtLeast, which seems hacky (like a Thread.sleep would)

Matt
  • 11,523
  • 2
  • 23
  • 33

2 Answers2

16

When Espresso waits, it actually does take in account MessageQueue, but in a different way from what you think. To be idle, the queue must either be empty, or have tasks to be run in more than 15 milliseconds from now.

You can check the code yourself, especially the method loopUntil() in UiControllerImpl.java and the file QueueInterrogator.java. In the latter file you will also find the logic of how Espresso checks the MessageQueue (method determineQueueState()).

Now, how to solve your problem? There are many ways:

  1. Use AsyncTask instead of Handler, sleeping on the background thread and executing actions onPostExecute(). This does the trick because Espresso will wait for AsyncTask to finish, but you might not like the overhead of another thread.

  2. Sleep in your test code, but you don't like that approach already.

  3. Write your custom IdlingResource: this is a general mechanism to let Espresso know when something is idle so that it can run actions and assertions. For this approach you could:

    • Use the class CountingIdlingResource that comes with Espresso

    • Call increment() when you post your runnable and decrement() inside the runnable after your logic has run

    • Register your IdlingResource in the test setup and unregister it in the tear down

See also: docs and sample, another sample

Community
  • 1
  • 1
Gil Vegliach
  • 3,542
  • 2
  • 25
  • 37
  • 1
    i'm thinking i might just abstract out the pushing of the message onto the queue, and inject some other implementation which acts as an IdlingResource. I'll have a default implementation in my code which defers to the handler, and in the test code one which is an IdlingResource and icnrements/decrements – Matt Aug 22 '15 at 20:54
0

As far as I know there is no wait for activity to finish method in espresso. You could implement your own version of waitForCondition, something robotium has. That way you'll only wait for as long as is needed and you can detect issues with your activity not finishing.

You'd basically poll your condition every x ms, something like.

while (!conditionIsMet() && currentTime < timeOut){
    sleep(100);
}

boolean conditionIsMet() {
    return "espresso check for if your splash view exists";
}
JohanShogun
  • 2,956
  • 21
  • 30
  • The problem is that android provides you no way to really look into the message queue to see if anything is queued up. It would have to be something that was intercepted and replaced at startup time (which i think is what robotium does). – Matt Aug 21 '15 at 18:29
  • The waitForCondition class just polls as condition you write yourself. As for robotium checking the queue, I do not believe so, I've seen issues like the you're having with robotium too. – JohanShogun Aug 21 '15 at 19:21
  • For this use case you should be able to wait for the action to have taken place, instead of looking at the message queue (which is really more interesting from a test stand point). – JohanShogun Aug 21 '15 at 19:22