20

Im trying to use Espresso to test my UI. When i login into my application, i do a call to Parse API (network call) to verify the username and password. If all is well the user gets directed to a new activity. I want to test this, but i cant seems to work with the idle resource thing.

Code:

public class ApplicationTest extends ActivityInstrumentationTestCase2<LoginActivity> {


private CountingIdlingResource fooServerIdlingResource;

public ApplicationTest() {
    super(LoginActivity.class);
}

@Before
public void setUp() throws Exception {
    super.setUp();
    injectInstrumentation(InstrumentationRegistry.getInstrumentation());
    getActivity();
    CountingIdlingResource countingResource = new CountingIdlingResource("FooServerCalls");
    this.fooServerIdlingResource = countingResource;
    Espresso.registerIdlingResources(countingResource);
}


public void testChangeText_sameActivity() {
    // Type text and then press the button.
    onView(withId(R.id.username))
            .perform(typeText("s@s.nl"), closeSoftKeyboard());
    onView(withId(R.id.password))
            .perform(typeText("s"), closeSoftKeyboard());

    if(performClick())
        onView(withId(R.id.main_relative_layout))
                .check(matches(isDisplayed()));
    // Check that the text was changed.
}

public boolean performClick(){
    fooServerIdlingResource.increment();
    try {
        onView(withId(R.id.login)).perform(click());
        return true;
    } finally {
        fooServerIdlingResource.decrement();
    }
}


@SuppressWarnings("javadoc")
public final class CountingIdlingResource implements IdlingResource {
    private static final String TAG = "CountingIdlingResource";
    private final String resourceName;
    private final AtomicInteger counter = new AtomicInteger(0);
    private final boolean debugCounting;

    // written from main thread, read from any thread.
    private volatile ResourceCallback resourceCallback;

    // read/written from any thread - used for debugging messages.
    private volatile long becameBusyAt = 0;
    private volatile long becameIdleAt = 0;

    /**
     * Creates a CountingIdlingResource without debug tracing.
     *
     * @param resourceName the resource name this resource should report to Espresso.
     */
    public CountingIdlingResource(String resourceName) {
        this(resourceName, false);
    }

    /**
     * Creates a CountingIdlingResource.
     *
     * @param resourceName  the resource name this resource should report to Espresso.
     * @param debugCounting if true increment & decrement calls will print trace information to logs.
     */
    public CountingIdlingResource(String resourceName, boolean debugCounting) {
        this.resourceName = checkNotNull(resourceName);
        this.debugCounting = debugCounting;
    }

    @Override
    public String getName() {
        return resourceName;
    }

    @Override
    public boolean isIdleNow() {
        return counter.get() == 0;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }

    /**
     * Increments the count of in-flight transactions to the resource being monitored.
     * <p/>
     * This method can be called from any thread.
     */
    public void increment() {
        int counterVal = counter.getAndIncrement();
        if (0 == counterVal) {
            becameBusyAt = SystemClock.uptimeMillis();
        }

        if (debugCounting) {
            Log.i(TAG, "Resource: " + resourceName + " in-use-count incremented to: " + (counterVal + 1));
        }
    }

    /**
     * Decrements the count of in-flight transactions to the resource being monitored.
     * <p/>
     * If this operation results in the counter falling below 0 - an exception is raised.
     *
     * @throws IllegalStateException if the counter is below 0.
     */
    public void decrement() {
        int counterVal = counter.decrementAndGet();

        if (counterVal == 0) {
            // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
            if (null != resourceCallback) {
                resourceCallback.onTransitionToIdle();
            }
            becameIdleAt = SystemClock.uptimeMillis();
        }

        if (debugCounting) {
            if (counterVal == 0) {
                Log.i(TAG, "Resource: " + resourceName + " went idle! (Time spent not idle: " +
                        (becameIdleAt - becameBusyAt) + ")");
            } else {
                Log.i(TAG, "Resource: " + resourceName + " in-use-count decremented to: " + counterVal);
            }
        }
        checkState(counterVal > -1, "Counter has been corrupted!");
    }

    /**
     * Prints the current state of this resource to the logcat at info level.
     */
    public void dumpStateToLogs() {
        StringBuilder message = new StringBuilder("Resource: ")
                .append(resourceName)
                .append(" inflight transaction count: ")
                .append(counter.get());
        if (0 == becameBusyAt) {
            Log.i(TAG, message.append(" and has never been busy!").toString());
        } else {
            message.append(" and was last busy at: ")
                    .append(becameBusyAt);
            if (0 == becameIdleAt) {
                Log.w(TAG, message.append(" AND NEVER WENT IDLE!").toString());
            } else {
                message.append(" and last went idle at: ")
                        .append(becameIdleAt);
                Log.i(TAG, message.toString());
            }
        }
    }
}

}

The exception i get now is the following:

ndroid.support.test.espresso.IdlingResourceTimeoutException: Wait for [FooServerCalls] to become idle timed out

When i run the test, the username and password are getting filled in but the perform click is never called and i get the exception after a few seconds. How should i implement the idle resource correctly?

EDIT --

I would recommend using Calabash for Android. Calabash works similar but doesn't need you to change your app code for testing.

Dennis Anderson
  • 1,348
  • 2
  • 14
  • 33

4 Answers4

25

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.

Community
  • 1
  • 1
appoll
  • 2,910
  • 28
  • 40
  • I wIll try this out tonight !! thanks ! will get back to you :) – Dennis Anderson Jun 14 '15 at 11:30
  • Good luck, It may seem like a bit of extra work, but logging was extremely helpful for me – appoll Jun 14 '15 at 12:00
  • I tried it and it worked !! i was way off with my code ! didn't aspected to implement any code in my "app" code. Its a real downside to change my app code to make my tests work. I got alot of Parse API calls. I believe my code is going to be a mess with all this ! But that was not the question, you have answered my real question. So thank you for that ! – Dennis Anderson Jun 15 '15 at 08:43
  • 1
    I'm glad it helped :) Maybe it's not the only way, but hey, not everyone writes ui tests AND handles networking at the same time. So I'm assuming it's worth it. All the best! – appoll Jun 15 '15 at 08:54
  • You can do something similar by looking for the progress view/fragment's visibility instead of adding the `isInProgress()` solely for the use of your test code. – JohnnyLambada Oct 23 '15 at 20:52
  • Doesn't this cause a memory leak since your idling resource now has a strong reference to the activity? – Christopher Francisco Jan 29 '16 at 19:00
  • It works great, I've used it with [otto](https://square.github.io/otto/) library, so I do not have to worry about listeners. – David Novák Dec 29 '16 at 12:56
  • Nice solution but it is deprecated. The reason and workaround is here https://stackoverflow.com/a/47079964/124253 – Sergey Dirin Jan 03 '18 at 06:35
  • Outdated solution. onTransitionToIdle is deprecated. Check here: https://www.repeato.app/espresso-wait-for-netowork-call/ – stoefln Aug 27 '20 at 16:22
9

The answers 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

stoefln
  • 14,498
  • 18
  • 79
  • 138
  • This is great, thanks! How do I wait for a real network call instead of the simulated 3 seconds delay? I'd like to get a callback from my network call when its done which also runs in a coroutine. – Ali Kazi Jun 07 '20 at 14:44
  • How do you use this in a test? – Haider Malik Jun 13 '20 at 22:30
  • registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback), you can get it https://developer.android.com/reference/androidx/test/espresso/idling/CountingIdlingResource – BollMose Nov 28 '20 at 13:39
  • 1
    Do we have to add code in production just to make test wait for it? – Skizo-ozᴉʞS ツ Apr 20 '21 at 14:08
  • It is a good practice to *do not have* a test code in the production code. I haven't found yet a better alternative yet, but it worth to mention it under offered solution. – Gleichmut Feb 13 '23 at 07:49
  • Although, google team offers a different approach: *When adding idling resources into your app, we highly recommend placing the idling resource logic in the app itself and performing only the registration and unregistration operations in your tests.* https://developer.android.com/training/testing/espresso/idling-resource – Gleichmut Feb 13 '23 at 07:51
2

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.
Daniel Lubarov
  • 7,796
  • 1
  • 37
  • 56
1

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.

Clive Jefferies
  • 1,138
  • 14
  • 26