Search code examples
androidandroid-fragmentsandroid-espressojunit-rule

Testing a PreferenceFragment does not work with Espresso and JUnit-Rules or ActivityInstrumentationTestCase2


I am working on an app with a settings screen, which i want to test with espresso. In this settings screen i add a PreferenceFragment like this:

setContentView(R.layout.settings_activity);

...

getFragmentManager().beginTransaction().replace(R.id.content_frame, new V4SettingsFragment()).commit();

settings_activity

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<include layout="@layout/toolbar" />

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@id/content_frame"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:id="@+id/shadow_prelollipop"
        android:background="@drawable/shadow_toolbar" />
</FrameLayout>

This settings screen works perfectly fine when i use it in a normal way, but something gets strange when i try to check and change preferences in the espresso test.

AndroidJUnit4 test

@Rule
public ActivityTestRule<V4SettingsActivity> settingsActivity = new ActivityTestRule<>(V4SettingsActivity.class);

@Test
public void checkFragement(){

    Resources resources = settingsActivity.getActivity().getResources();


    Matcher<View> adapterMatcher = allOf(isDescendantOfA(withId(R.id.content_frame)), withId(android.R.id.list));

    onData(allOf(
            is(instanceOf(Preference.class)),
            withKey(resources.getString(R.string.prefs_category_display_preferredviewer))))
           .inAdapterView(adapterMatcher);
}

This is the error i get from Espresso:

....CustomFailureHandler$NoHierarchyException: android.support.test.espresso.AmbiguousViewMatcherException: Multiple Ambiguous Views found for matcher (is descendant of a: with id: ....:id/content_frame and with id: android:id/list)

So i investigated further and found out that the layout of my Settings page looks different when i use the app normally, and when i use it in the Espresso test. Normally inside the LinearLayout with the id content_frame there is only one child (which has the id android:id/list. But when i run the Espresso test i see that the same LinearLayout view contains 2 childs.

When i run following code inside the Espresso test:

ViewGroup viewById = (ViewGroup) settingsActivity.getActivity().findViewById(R.id.content_frame);
    for(int i=0; i<viewById.getChildCount(); i++){
        ViewGroup vgChild = (ViewGroup) viewById.getChildAt(i);
        Log.d(TAG, "switchDirectToHtmlReader() " + vgChild.toString()); // called two times during espresso test
    }

i get following output:

D: switchDirectToHtmlReader() android.widget.ListView{2854b6d9 V.ED.VC. ......ID 0,0-1080,0 #102000a android:id/list}
D: switchDirectToHtmlReader() android.widget.LinearLayout{37eb976a     V.E..... ........ 0,0-1080,1677}

but when i run a similar code not in the espresso test but in the activity i get following output:

D: onCreate() android.widget.ListView{149b8a90 V.ED.VC. ......I. 0,0-0,0 #102000a android:id/list}

So instead of only one child with the id android:id/list which i get when i use the app normally, i get two childs when i run the espresso test.

What do i do wrong, why does the hierarchy look so different from the hierarchie in the normal use?


Solution

  • i don't really understand it, but this solved my problem: if i add following lines

    ViewGroup vgParent = (ViewGroup)findViewById(R.id.content_frame);
    vgParent.removeView(findViewById(android.R.id.list));
    

    before

    getFragmentManager().beginTransaction().replace(R.id.content_frame, new V4SettingsFragment()).commit();
    

    then i have only one child inside R.id.content_frame during the espresso test (just as when i use the app normally), and my espresso tests work fine.