24

I have RecyclerView with endless scrolling. So when user reach last - 2 position in list, I call server for more data and during call in progress, I add one more item - progress one.

Now, I'm trying to write decent UI test with Espresso which will check if endless scrolling is working currently:

    @Test
    public void checkIfProgressShown() {
        InstaFeed feed = TestDataFactory.makeInstaFeed(20);
        InstaFeed oldFeed = TestDataFactory.makeInstaFeed(20);
               when(mockDataManager.getFeedItemsFromServer()).thenReturn(Observable.just(feed.getInstaItems()));
        when(mockDataManager.getOldFeedItemsFromServer()).thenReturn(Observable.just(oldFeed.getInstaItems())
                .delay(2, TimeUnit.SECONDS));

        instaActivityActivityTestRule.launchActivity(null);

        int position = 0;
        for (InstaItem item : feed.getInstaItems()) {
            onView(withId(R.id.recycler_view))
               .perform(RecyclerViewActions.scrollToPosition(position));
            onView(withText(item.getLocation().getName()))
                .check(matches(isDisplayed())); // Line of crash
            position++;
        }

        onView(withId(R.id.progress))
                .check(matches(isDisplayed()));
    }

So basically saying, I'm trying to delay response from Observable with new batch of items to show, in order Espresso can scroll down to last item and make my progress item visible. Problem is here that test just stacks and from UI point of view it looks like:

Don't be confused that there is no progress bar here - animation is disabled on device, so it's ok. Last, small item is item with progress

And after 60 seconds it crashes -

android.support.test.espresso.AppNotIdleException: Looped for 3019 iterations over 60 SECONDS. The following Idle Conditions failed .
at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:580)
at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:82)
at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:53)
at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:184)
at android.support.test.espresso.ViewInteraction.check(ViewInteraction.java:158)
at org.kidinov.mvp_test.InstaActivityTest.checkIfProgressShown(InstaActivityTest.java:69)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at android.support.test.internal.statement.UiThreadStatement.evaluate(UiThreadStatement.java:55)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:257)
at org.kidinov.mvp_test.test.common.TestComponentRule$1.evaluate(TestComponentRule.java:49)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:54)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:240)
at org.kidinov.mvp_test.runner.UnlockDeviceAndroidJUnitRunner.onStart(UnlockDeviceAndroidJUnitRunner.java:36)
at org.kidinov.mvp_test.runner.RxAndroidJUnitRunner.onStart(RxAndroidJUnitRunner.java:16)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1853)

I use RxAndroidJUnitRunner as a Runner.

Probably I'm doing it wrong and there is no need to do simulate delay?


UPD: There is prepared project in order if you'd like to play with that situation. Problem inside that class.

Divers
  • 9,531
  • 7
  • 45
  • 88
  • There is a lot of possibilities that causing your main thread to go freeze. Are there any way the computation scheduler is is hooked to immediate scheduler? Or could check if you add subscribeOn and observeOn on your delayed observable that problem still persist? – Andri Ihsannudin Jul 11 '18 at 04:41
  • https://android.jlelse.eu/integrate-espresso-idling-resources-in-your-app-to-build-flexible-ui-tests-c779e24f5057 – Konrad Morawski Sep 30 '18 at 16:37

2 Answers2

0

I spend some time and fixed your test. Hope it will help you.

1.You are generating the test data in a wrong way. In makeInstaItem you pass as i indexes (and so 0 value) so the last item in the list has the same created time (0L) as progress item created by InstaAdapter.:

private static InstaItem makeInstaItem(String prefix, int i, int dateСoefficient) {
    InstaItem item = new InstaItem();
    item.setLocation(new Location(prefix + "_location_" + i));
    item.setCreatedTime(i * 1000000L * dateСoefficient);
    item.setImages(makeInstaImages());
    item.setId(randomUuid());
    return item;
}

This should be changed to for example:

private static InstaItem makeInstaItem(String prefix, int i, int dateСoefficient) {
    InstaItem item = new InstaItem();
    item.setLocation(new Location(prefix + "_location_" + i));
    item.setCreatedTime((i + 1) * 1000000L * dateСoefficient);
    item.setImages(makeInstaImages());
    item.setId(randomUuid());
    return item;
}

Created test list is passed as reference to your adapter. After progress item is added it is one before last item. In tests you are trying to match this progress bar item like it would be regular location item, because of this it crashes. After all my changes its is added as last item and removed before loop in test reaches it.

2. In InstaPresenter you are not adding items when loadMore subscription is called:

subscribe(x -> {
    Timber.d("load more - items fetched");
}

Change to for example:

subscribe(items -> {
    if (!items.isEmpty()) {
        getMvpView().showFeed(items);
    }
    Timber.d("load more - items fetched");
}

3.You don't need delay at all. Keep implementation simple as for example:

when(mockDataManager.getOldFeedItemsFromServer()).thenReturn(Observable.just(oldFeed.getInstaItems())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext(x -> {
                Timber.d("On next after delay");
                when(mockDataManager.subscribeOnFeedItemsChanges())
                        .thenReturn(Observable.just(oldFeed.getInstaItems()));
            }));

instaActivityActivityTestRule.launchActivity(null);

int position = 0;
for (InstaItem item : feed.getInstaItems()) {
    onView(withId(R.id.recycler_view))
                .perform(RecyclerViewActions.scrollToPosition(position));
    onView(withText(item.getLocation().getName()))
                .check(matches(isDisplayed()));
    onView(withText(InstaAdapter.dateFormat.format(new Date(item.getCreatedTime() * 1000))))
                .check(matches(isDisplayed()));
    position++;
}

for (InstaItem item : oldFeed.getInstaItems()) {
    onView(withId(R.id.recycler_view))
                .perform(RecyclerViewActions.scrollToPosition(position));
    onView(withText(item.getLocation().getName()))
                .check(matches(isDisplayed()));
    onView(withText(InstaAdapter.dateFormat.format(new Date(item.getCreatedTime() * 1000))))
                .check(matches(isDisplayed()));
    position++;
}
  1. Just my small personal suggestion. I would rather test presenter logic with unit tests than struggle with UI testing.

I recorded the video to show how it works after above changes

sswierczek
  • 810
  • 1
  • 12
  • 19
  • I'm sorry to say that, but you got my question and code wrong. Also your forth sugestion is wrong - you need to check both sides, not only presenter, if you want to test app completly. – Divers Aug 14 '16 at 17:06
  • @Divers could you explain a little bit more? If you want to see progress just leave delay as you have in your code, and between loops add, 1. scroll to progress `onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.scrollToPosition(position));` and after that, 2. wait for first item from loaded collection `onView(isRoot()).perform(waitText(oldFeed.getInstaItems().get(0).getLocation().getName(), 3000));`. Where `waitText` is the same as your `waitId` besides it matches by text `Matcher` – sswierczek Aug 14 '16 at 17:25
0

The referenced project no longer builds with recent versions of Android Studio and JDK 11.

When using Espresso with RxJava, you can use https://github.com/square/RxIdler to register the needed IdlingResources and wrap any custom Rx schedulers.

yogurtearl
  • 3,035
  • 18
  • 24