2

I try using Espresso IdlingResource to wait for a background task started in Activity.onCreate.

I saw similar question here and there but the proposed solution aren't working for me.

I added a waitForIdle(long timeout) method in my Activity which I call from my @Before method and this approach works but is a bit invasive in the application code and IdlingResource are meant for this purpose.

From the given links I understood the @Before is called after Activity.onCreate and then we register the IdlingResource too late.

My IdlingResource needs the Activity instance to determine if it's idle or not. Using ActivityTestRule.beforeActivityLaunched just allow me to create an "empty" IdlingResource (getActivity() is null at this point) and register it. Then in @Before I set the Activity to my IdlingResource but Espresso still doesn't wait for it. The isIdleNow is not even called.

(I'm not very enthusiast with the idea of overriding the test runner for this purpose)

Here follows the snippets used to test:

MainActivity:

public class MainActivity extends AppCompatActivity
{
  private Object mIdleMonitor = new Object();
  private boolean mIdle;

  private TextView mHello;
  private View mProgress;

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mHello = findViewById(R.id.main_hello);
    mProgress = findViewById(R.id.main_progress);

    toggleWorkingState(true);

    new Thread(new Runnable()
    {
      @Override
      public void run()
      {
        SystemClock.sleep(5000);
        runOnUiThread(new Runnable()
        {
          @Override
          public void run()
          {
            mHello.setText(R.string.main_hello_label);
            toggleWorkingState(false);
          }
        });
      }
    }).start();
  }

  @UiThread
  private void toggleWorkingState(boolean working)
  {
    mProgress.setVisibility(working ? View.VISIBLE : View.GONE);
    synchronized(mIdleMonitor)
    {
      mIdle = !working;
      mIdleMonitor.notify();
    }
  }

  public boolean isIdle()
  {
    synchronized(mIdleMonitor)
    {
      return mIdle;
    }
  }

  public void waitForIdle(long timeout)
  {
    long end = System.currentTimeMillis() + timeout;
    synchronized(mIdleMonitor)
    {
      while(!mIdle && System.currentTimeMillis() < end)
      {
        try
        {
          mIdleMonitor.wait(timeout);
        }
        catch(InterruptedException e)
        {
          break;
        }
      }
    }
  }
}

MainActivityIdlingResource:

public class MainActivityIdlingResource implements IdlingResource
{

  private static int ID;
  private final int mId;

  private WeakReference<MainActivity> mObservable;

  @Nullable
  private ResourceCallback mCallback;

  private boolean mWasIdle = false;

  public MainActivityIdlingResource()
  {
    mId = ++ID;
  }

  public void setActivity(MainActivity observable)
  {
    mObservable = new WeakReference<>(observable);
  }

  @Override
  public String getName()
  {
    return MainActivityIdlingResource.class.getSimpleName() + "#" + mId;
  }

  @Override
  public boolean isIdleNow()
  {
    MainActivity observable = mObservable == null ? null : mObservable.get();
    boolean isIdle = observable != null && observable.isIdle();
    if (!mWasIdle && isIdle && mCallback != null)
    {
      mCallback.onTransitionToIdle();
    }
    mWasIdle = isIdle;
    return isIdle;
  }

  @Override
  public void registerIdleTransitionCallback(ResourceCallback callback)
  {
    mCallback = callback;
  }
}

MainActivityTest:

@RunWith(AndroidJUnit4.class)
public class MainActivityTest
{

  private IdlingRegistry mIdlingRegistry = IdlingRegistry.getInstance();

  @Rule
  public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<MainActivity>(MainActivity.class)
  {
    @Override
    protected void beforeActivityLaunched()
    {
      super.beforeActivityLaunched();
      mIdlingResource = new MainActivityIdlingResource();
      mIdlingRegistry.register(mIdlingResource);
    }

    @Override
    protected void afterActivityFinished()
    {
      super.afterActivityFinished();
      if (mIdlingResource != null)
      {
        mIdlingRegistry.unregister(mIdlingResource);
      }
    }
  };

  private MainActivity mActivity;

  private MainActivityIdlingResource mIdlingResource;

  /**
   * Source: https://stackoverflow.com/a/37864603/2551689
   */
  public static ViewAction replaceProgressBarDrawable()
  {
    return actionWithAssertions(new ViewAction()
    {
      @Override
      public Matcher<View> getConstraints()
      {
        return isAssignableFrom(ProgressBar.class);
      }

      @Override
      public String getDescription()
      {
        return "replace the ProgressBar drawable";
      }

      @Override
      public void perform(final UiController uiController, final View view)
      {
        // Replace the indeterminate drawable with a static red ColorDrawable
        ProgressBar progressBar = (ProgressBar) view;
        progressBar.setIndeterminateDrawable(new ColorDrawable(0xffff0000));
        uiController.loopMainThreadUntilIdle();
      }
    });
  }

  @Before
  public void setUp()
  {
    mActivity = mActivityRule.getActivity();
    mIdlingResource.setActivity(mActivity);
    // using this watcher, UiController.loopMainThreadUntilIdle() fixes the idle issue, don't understand why nor if it's mandatory
    // onView(withId(R.id.main_progress)).perform(replaceProgressBarDrawable());
    // using mActivity.waitForIdle(6000); do the job but is not the expected solution
  }

  @Test
  public void test_idle_state()
  {
    assertTrue(mActivity.isIdle());
    onView(withId(R.id.main_progress)).check(matches(not(isDisplayed())));
    onView(withText(R.string.main_hello_label)).check(matches(isDisplayed()));
  }
}
opatry
  • 131
  • 4
  • Since a `ProgressBar` is involved, I also used an indeterminate drawable replacement using a watcher (see [here](https://stackoverflow.com/a/37864603/2551689)) and the `UiController.loopMainThreadUntilIdle()` call inside makes the whole thing work as expected (called in `@Before`). Any thoughts? – opatry Nov 11 '17 at 12:20
  • I think Espresso.registerIdlingResource should be used instead of IdlingRegistry.register – Anatolii Mar 26 '18 at 15:56

0 Answers0