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