How to use Espresso Idling Resource for network calls

梦想的初衷 提交于 2019-11-27 18:36:21

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!