How to use Espresso Idling Resource for network calls
The ansers above seem kind of outdated for 2020. Nowadays don't need to create the CountingIdlingResource yourself. There is one already. You can create a singleton instance of it and access it in your activity code:
// CountingIdlingResourceSingleton.kt:
import androidx.test.espresso.idling.CountingIdlingResource
object CountingIdlingResourceSingleton {
private const val RESOURCE = "GLOBAL"
@JvmField val countingIdlingResource = CountingIdlingResource(RESOURCE)
fun increment() {
countingIdlingResource.increment()
}
fun decrement() {
if (!countingIdlingResource.isIdleNow) {
countingIdlingResource.decrement()
}
}
}
Then use it like this in your application code:
// MainActivity.kt:
start_activity_button.setOnClickListener {
val intent = Intent(context, LoginActivity::class.java)
CountingIdlingResourceSingleton.increment()
// I am using a kotlin coroutine to simulate a 3 second network request:
val job = GlobalScope.launch {
// our network call starts
delay(3000)
}
job.invokeOnCompletion {
// our network call ended!
CountingIdlingResourceSingleton.decrement()
startActivity(intent)
}
}
Then register your idling resource in the test:
// LoginTest.kt:
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance().register(CountingIdlingResourceSingleton.countingIdlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(CountingIdlingResourceSingleton.countingIdlingResource)
}
You can find additional information on my blog post about how to make espresso wait for network calls
Like the other answer suggests, the countingIdlingResource does not really apply for your use case.
What I always do is add an interface - let's call this one ProgressListener
- as a field of the activity / fragment that has a resource to be waited on (asynchronous background work, longer networking sessions, etc.) and a method to notify it everytime the progress is shown or dismissed.
I'm assuming you have your credentials validation logic and the call to the Parse API in the LoginActivity
, which will then call an intent to the MainActivity
if successful.
public class LoginActivity extends AppCompatActivity {
private ProgressListener mListener;
...
public interface ProgressListener {
public void onProgressShown();
public void onProgressDismissed();
}
public void setProgressListener(ProgressListener progressListener) {
mListener = progressListener;
}
...
public void onLoginButtonClicked (View view) {
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
// validate credentials for blanks and so on
// show progress and call parse login in background method
showProgress();
ParseUser.logInInBackground(username,password, new LogInCallback() {
@Override
public void done(ParseUser parseUser, ParseException e) {
dismissProgress();
if (e == null){
// Success!, continue to MainActivity via intent
Intent intent = new Intent (LoginActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
else {
// login failed dialog or similar.
}
}
});
}
private void showProgress() {
// show the progress and notify the listener
...
notifyListener(mListener);
}
private void dismissProgress() {
// hide the progress and notify the listener
...
notifyListener(mListener);
}
public boolean isInProgress() {
// return true if progress is visible
}
private void notifyListener(ProgressListener listener) {
if (listener == null){
return;
}
if (isInProgress()){
listener.onProgressShown();
}
else {
listener.onProgressDismissed();
}
}
}
Then, simply implement the IdlingResource class and override its methods to communicate when the resource goes from busy to idle through its ResourceCallBack
public class ProgressIdlingResource implements IdlingResource {
private ResourceCallback resourceCallback;
private LoginActivity loginActivity;
private LoginActivity.ProgressListener progressListener;
public ProgressIdlingResource(LoginActivity activity){
loginActivity = activity;
progressListener = new LoginActivity.ProgressListener() {
@Override
public void onProgressShown() {
}
@Override
public void onProgressDismissed() {
if (resourceCallback == null){
return ;
}
//Called when the resource goes from busy to idle.
resourceCallback.onTransitionToIdle();
}
};
loginActivity.setProgressListener (progressListener);
}
@Override
public String getName() {
return "My idling resource";
}
@Override
public boolean isIdleNow() {
// the resource becomes idle when the progress has been dismissed
return !loginActivity.isInProgress();
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
Last step is to register your custom idling resource in the test's setUp()
method:
Espresso.registerIdlingResources(new ProgressIdlingResource((LoginActivity) getActivity()));
And that's it! Now espresso will wait for your login process to complete and then continue with all the other tests.
Please let me know if I wasn't clear enough or if that is exactly what you needed.
Another approach is to have a custom Idling resource that can examine your activity. I have created one here:
public class RequestIdlingResource implements IdlingResource {
private ResourceCallback resourceCallback;
private boolean isIdle;
@Override
public String getName() {
return RequestIdlingResource.class.getName();
}
@Override
public boolean isIdleNow() {
if (isIdle) return true;
Activity activity = getCurrentActivity();
if (activity == null) return false;
idlingCheck(activity);
if (isIdle) {
resourceCallback.onTransitionToIdle();
}
return isIdle;
}
private Activity getCurrentActivity() {
final Activity[] activity = new Activity[1];
java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = Iterables.getOnlyElement(activities);
return activity[0];
}
@Override
public void registerIdleTransitionCallback(
ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
public void idlingCheck(Activity activity)
{
/*
Look up something (view or method call) on the activity to determine if it is idle or busy
*/
}
}
https://gist.github.com/clivejefferies/2c8701ef70dd8b30cc3b62a3762acdb7
I got the inspiration from here, which shows how it could be used in a test:
https://github.com/AzimoLabs/ConditionWatcher/blob/master/sample/src/androidTest/java/com/azimolabs/f1sherkk/conditionwatcherexample/IdlingResourceExampleTests.java
The good thing is that you do not have to add any test code to your implementation class.
Espresso will poll the idling resource just before performing the click (or any view action). But you don't decrement your counter until after the click. It's a deadlock.
I don't see any quick fix here; your approach doesn't really make sense to me. A few possible alternate approaches come to mind:
- Depending on what library you use for networking, you might be able to write an idling resource which checks if there's a call in progress.
- If you show a progress indicator while the login call is in progress, you could install an IdlingResource which waits for that to disappear.
- You could wait for the next activity to be launched, or for a certain view to appear/disappear.