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()));
}
}