10

I am trying to test RecyclerView with AndroidJunit4, it is my test code:

@Rule
    public ActivityTestRule<ProductListActivity> rule  = new  ActivityTestRule<>(ProductListActivity.class);

............................
..........................

@Test
    public void ensureDataIsLoadingOnSuccess() throws Exception {
        ProductListActivity activity = rule.getActivity();
        ...........................
        ............

        activity.runOnUiThread(new Runnable() {
        public void run() {
            activity.displayProducts(asList(product1, product2), 0);
        }
    });

        assertEquals(2, mAdapter.getItemCount());
        assertThat(((ProductAdapter) mAdapter).getItemAtPosition(0),sameInstance(product1));
        assertThat(((ProductAdapter) mAdapter).getItemAtPosition(1),sameInstance(product2));


    }

Here is my code for displayProducts() in Activity:

@Override
    public void displayProducts(List<Product> products, Integer pageNo) {
        progressBar.setVisibility(View.GONE);
        if (pageNo == 0 && products.size() == 0) {
            noProductTextView.setVisibility(View.VISIBLE);
        } else {
            mProductAdapter.addProduct(products);
            noProductTextView.setVisibility(View.GONE);
            productListView.setVisibility(View.VISIBLE);
        }
    }

It is giving error like:

junit.framework.AssertionFailedError: expected:<2> but was:<0>
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.failNotEquals(Assert.java:287)
at junit.framework.Assert.assertEquals(Assert.java:67)
at junit.framework.Assert.assertEquals(Assert.java:199)
at junit.framework.Assert.assertEquals(Assert.java:205)
at com.kaushik.myredmart.ui.ProductListActivityTest.ensureDataIsLoadingOnSuccess(ProductListActivityTest.java:94)

Please help what is the problem in my code?

dev_android
  • 8,698
  • 22
  • 91
  • 148
  • How do you setup `mAdapter`? – tynn May 13 '17 at 14:30
  • 1
    Animations are your enemy in ui testing, every progres bar, custom animations or any work being done on non async thread pool will not be registered with espresso and it will just run trough the other assertions because it thinks there is nothing to wait. Also try to write your tests that you inject and set the data in setup and teardown then your activity will have it during buildup. Did you call notifydataset changed after displayProducts()? – originx May 19 '17 at 16:43
  • where does mAdapter come from? I think you are checking the wrong adapter – Tudor May 20 '17 at 07:39

2 Answers2

0

The reason is that your Espresso test did not wait your loading task which is time-consuming. You need to use a espresso-idling-resource to tell it to wait this task to finish.

Then you need a class to implement IdlingResource and declare it your Activity.

When your Espresso test run, it will know and wait your long-time consuming task to complete and test the result.

Firstly, add its dependency.

 compile "com.android.support.test.espresso:espresso-idling-resource:2.2.2"

Secondly, you need two Java files in folder src/main/java/your-package.
SimpleCountingIdlingResource.java

public final class SimpleCountingIdlingResource implements IdlingResource {

  private final String mResourceName;

  private final AtomicInteger counter = new AtomicInteger(0);

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

  /**
   * Creates a SimpleCountingIdlingResource
   *
   * @param resourceName the resource name this resource should report to Espresso.
   */
  public SimpleCountingIdlingResource(String resourceName) {
    mResourceName = checkNotNull(resourceName);
  }

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

  @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.
   */
  public void increment() {
    counter.getAndIncrement();
  }

  /**
   * Decrements the count of in-flight transactions to the resource being monitored.
   *
   * 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();
      }
    }

    if (counterVal < 0) {
      throw new IllegalArgumentException("Counter has been corrupted!");
    }
  }
}

EspressoIdlingResource.java

public class EspressoIdlingResource {

  private static final String RESOURCE = "GLOBAL";

  private static SimpleCountingIdlingResource mCountingIdlingResource =
      new SimpleCountingIdlingResource(RESOURCE);

  public static void increment() {
    mCountingIdlingResource.increment();
  }

  public static void decrement() {
    mCountingIdlingResource.decrement();
  }

  public static IdlingResource getIdlingResource() {
    return mCountingIdlingResource;
  }
}

Ok. Let's go to Activity where you have a time-consuming task. Firstly, put this method at the bottom.

@VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }

Inside your time-consuming task. you should tell your Espresso to wait like this.

EspressoIdlingResource.increment();

  yourTask.run(new Callback() {
    void onFinish(){
      EspressoIdlingResource.decrement();
    }
  })

Final step is to define these methods in your UI test class.

@Before
public void registerIdlingResource() {
    Espresso.registerIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
}

/**
 * Unregisters your idling resource so it can be garbage collected and does not leak any memory
 */
@After
public void unregisterIdlingResource() {
    Espresso.unregisterIdlingResources(mOnBoardActivityTestRule.getActivity().getCountingIdlingResource());
}

Yeah. Finally we done.

Quang Nguyen
  • 2,600
  • 2
  • 17
  • 24
-1

There is one problem I can see here, your are inquiring the List size before the Main/UI thread is able to update it. So, you will have to wait in the current thread till the Activity finished updating the list on Main thread.

You can do,

Thread.sleep(500);

in the Test class to wait, to test the list setting behavior in Activity and you will find the assertion to be valid.

Since, the main thread runs infinitely till the application is running, you will have to implement a callback interface provided by the Activity to be informed about when populating the list is finished.

Manish Kumar Sharma
  • 12,982
  • 9
  • 58
  • 105
  • The question is why Espresso does not wait until `MessageQueue` is empty? – azizbekian May 13 '17 at 14:10
  • @azizbekian because it's running on another thread (background). Add an observer design pattern and catch it on the exact time it's happing otherwise use Thread sleep method. – Avi Levin May 13 '17 at 14:20
  • Your clarification is not true: Espresso runs on a background thread, **but** it listens for the `MessageQueue` of the UI thread to be empty, and as soon it is empty only after that Espresso will continue with matchers. You are suggesting a workaround with your answer, but not clarifying why the issue happens. – azizbekian May 13 '17 at 14:28
  • Then what is actula reason and what is proper proper workaround for it?@azizbekian – dev_android May 15 '17 at 03:01
  • I couldn't come up with a satisfactory clarification, that's why I haven't posted an answer. Judging by the code you provide, this should work, because Espresso should be aware of those changes and should wait until all messages are consumed from `MessageQueue`. – azizbekian May 18 '17 at 09:00
  • Thia should never be used in testing this brings flakynes especially on different devices (imagine running this code on some ancient samsung galaxy ace cluttered with other apps. You should use idling resources to tell your test to wait until networking, db calls etc are finished – originx May 19 '17 at 16:38