14

I'm writing UI tests with Espresso. App cooperates tightly with server, so in many cases, I need to wait for either value to be calculated, or data is got and displayed, etc. Espresso suggests using IdlingResource for this. My IdlingResource classes look like this (simple and clear example).

public class IRViewVisible implements IdlingResource {

private View view;
private ResourceCallback callback;

public IRViewVisible(View view) {
    this.view = view;
}

@Override
public String getName() {
    return IRViewVisible.class.getName();
}

@Override
public boolean isIdleNow() {
    if(view.getVisibility() == View.VISIBLE && callback != null) {
        callback.onTransitionToIdle();
        return true;
    }
    return false;
}

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

Please correct me if I'm wrong anywhere (as sometimes it seems to me that my IdlingResources do not work properly). I register the idling resource in setUp() like this:

IRViewVisible ir = new IRViewVisible(View v);
Espresso.registerIdlingResources(ir).

Unregister it on tearDown().

I found this article (there is a section called "Register a component tied to an Activity instance") — I do not use his schema, but I checked hashcode of view that was set to IdlingResource after registering (in each method), and it's not the same view — all hashes are different.

Another question: One Test class (it's results) can't have any effect on another Test class, can it?

Wandering Fool
  • 2,170
  • 3
  • 18
  • 48
Lanitka
  • 922
  • 1
  • 10
  • 20
  • Can you explain the behavior of this custom idling resource when it does not work properly? Also can you explain again what you do with hashcodes and why? To answer a bit here, tests **should** be independent from one another but it is your responsibility to do so. An easy way **not** to follow this best practise is to rely on global state in your code, e.g. static fields – Gil Vegliach Aug 15 '15 at 08:45
  • about when it doesn't work: I register this IdlingResource, do NOT get timeout idling exception but test fails (it doesn't happen always, just sometimes but I want to understand why..) because, e.g. cannot read value from filed, caused by View.visibility is GONE.. And sure, my tests are totally independent from each other. – Lanitka Aug 15 '15 at 22:16
  • And with hashcode.. If I understood the article right and if the author was right with his idea (see link), the framework after calling Espresso.registerIdlingResource(ir) more that onece, using the same type of idlingResource do not contain actual link to Activity so View as well.. so I decided to check if my class contains the actual View or the old one.. – Lanitka Aug 15 '15 at 22:25
  • 1
    Espresso uses IdlingResource.getName() as identifiers for idling resources, so that if you register two resources with the same name only one will be used. In your code **all** the resources have the same name, which means only the first you register will work. Maybe this helps, I'm still not sure what the role of hashCode here is. – Gil Vegliach Aug 16 '15 at 08:00

3 Answers3

4

I'm guessing your problem stems from getName() returning the same name for all instances of IRViewVisible. This means you can only have one registered instance of it at a time - any subsequent registrations will fail (silently!).

You mention that you clear the IdlingResources at the end of each test, but if you are register multiple instances of it at once, you need to make sure each instance has a unique name. it's not clear from your question if you're registering multiple instances of IRViewVisible in a single test.

As to your final question: Yes, it is possible. Android doesn't completely shut down the Application between test runs - just the Activities. Common things which can cause problems:

  • Failing to clear persistent state (saved data).
  • Failing to clear global state - e.g. static variables/singletons
  • Not waiting for background threads to finish running.

As an aside, it's worth noting that you only call onTransitionToIdle() inside isIdleNow(). This works (thanks @Be_Negative for the heads up!) but it could slow down your tests a lot, since Espresso will only poll isIdleNow() every few seconds. If you call onTransitionToIdle() as soon as the view becomes visible, it should speed things up considerably.

I needed something similar to your IRViewVisible myself, here's my effort.

vaughandroid
  • 4,315
  • 1
  • 27
  • 33
  • 1
    1. Espresso polls isIdleNow() every 5 seconds if onTransitionToIdle() isn't explicitly called. 2. It was mentioned that idlingResource is unregistered in tearDown. In this case getName() doesn't matter that much as long as tearDown method is called. – Be_Negative Aug 19 '15 at 21:28
  • @Be_Negative: Re. 1: Where did you get this information? I'll update my answer if you can point me to proof. :) Re. 2: It's going to be a problem if there is more than one instance of IRViewVisible being used in a single test. I'll update my answer to make this clear. – vaughandroid Aug 20 '15 at 07:48
  • @Be_Negative: OK, confirmed you are correct so I'll update my answer. – vaughandroid Aug 21 '15 at 07:52
  • @Be_Negative: where in the Espresso code did you find the 5-second polling logic? – Gil Vegliach Aug 29 '15 at 17:01
  • @GilVegliach I actually didn't find where 5 seconds is coming from. But if you add a log statement into you isIdleNow() method of your IdlingResource you will notice that it's being called every 5 seconds. – Be_Negative Aug 29 '15 at 18:13
  • @vaughandroid: can you please tell me how you confirmed Be_Negative's statement? The point is that from the code there seems to be no polling, see `UiControllerImpl.loopUntil(EnumSet conditions)`. – Gil Vegliach Aug 29 '15 at 19:05
  • @Be_Negative: thanks, I'll check that as soon as I have some time. In the meanwhile, read my comment above. – Gil Vegliach Aug 29 '15 at 19:06
  • @vaughandroid thank you for your answer. In my code I have only several tests when register the same idlingresource several times in a single test (in others - just once for a test). But I call Espresso.unregisterIdlingResources before a next call, so I think it shouldn't cause the problem. But your suggestion with static variables and singletons may be the problem. I'll check this. – Lanitka Aug 31 '15 at 07:26
1

So the isIdleNow() method will never return true if you don't set a callback to the idlingResource? I reckon it's better to refactor it like this:

@Override
public boolean isIdleNow() {
    boolean idle = view.getVisibility() == View.VISIBLE;
    if(idle && callback != null) {
        callback.onTransitionToIdle();
    }
    return idle;
} 
phq
  • 976
  • 1
  • 6
  • 3
  • 1
    Invoke transitions to the idle state outside idle checks. see: https://developer.android.com/training/testing/espresso/idling-resource – Nevin Chen May 24 '18 at 08:33
-2

Well, first of all you shouldn't need to use Espresso IdlingResource to test server calls. If you use AsyncTasks in your server calls, Espresso will be able to know when to be idle and when not. If this is not enough: try to refactor your code in this way:

  IRViewVisible idlingResource = new IRViewVisible(yourView);
  IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
        IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

        // Now we wait
        Espresso.registerIdlingResources(idlingResource);

        // Stop and verify


        // Clean up
        Espresso.unregisterIdlingResources(idlingResource);

Hope to be helpful.

Lorenzo Camaione
  • 505
  • 3
  • 15