Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Android PreferenceFragment with Espresso

I am trying to write tests for the PreferenceFragments fragment in Settings.

However, I've been getting this error: android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: is assignable from class: class android.widget.AdapterView

The code for the Test is the following:

@RunWith(AndroidJUnit4.class)
@SmallTest
public class SettingsFragmentTest {

    @Rule
    public ActivityTestRule<SettingsActivity> mActivityRule = new ActivityTestRule<>(
            SettingsActivity.class);

    @Test
    public void preferredLocationShouldBeVisibleOnDisplay(){
        mActivityRule.getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                SettingsFragment settingsFragment = startSettingsFragment();
            }
        });

        // This check passes correctly
        onView(withId(R.id.weather_settings_fragment))
                .check(matches(isCompletelyDisplayed()));

        // This check gives me the NoMatchingViewException
        onData(allOf(is(instanceOf(Preference.class)),
                withKey("location")))
                .check(matches(isCompletelyDisplayed()));
    }

    private SettingsFragment startSettingsFragment(){
        SettingsActivity activity = mActivityRule.getActivity();
        FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
        SettingsFragment settingsFragment = new SettingsFragment();

        transaction.replace(R.id.weather_settings_fragment, settingsFragment, "settingsFragment");
        transaction.commit();

        return settingsFragment;
    }
}

The settings_activity layout looks as follows:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
          android:name="com.example.android.sunshine.SettingsFragment"
          android:id="@+id/weather_settings_fragment"
          android:layout_width="match_parent"
          android:layout_height="match_parent" />

And the Preferences Screen layout is the following:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent">

    <EditTextPreference
        android:defaultValue="@string/pref_location_default"
        android:inputType="text"
        android:key="@string/pref_location_key"
        android:singleLine="true"
        android:title="@string/pref_location_label" />

    <ListPreference
        android:defaultValue="@string/pref_units_metric"
        android:entries="@array/pref_units_options"
        android:entryValues="@array/pref_units_values"
        android:key="@string/pref_units_key"
    android:title="@string/pref_units_label" />

<CheckBoxPreference
    android:defaultValue="@bool/show_notifications_by_default"
    android:key="@string/pref_enable_notifications_key"
    android:summaryOff="@string/pref_enable_notifications_false"
    android:summaryOn="@string/pref_enable_notifications_true"
    android:title="@string/pref_enable_notifications_label" />

</PreferenceScreen>

I haven't been able to find any examples or information online on how to test PreferenceFragments. Most of the information related to testing Activities.

like image 332
Marco Poloe Avatar asked Jul 18 '17 16:07

Marco Poloe


People also ask

What is espresso in mobile testing?

Espresso created by Google is a native framework for Android automated testing. The tool is a part of the Android SDK and is easy to use for native mobile development. Thanks to Espresso, you can create tests that are close to the Android app's logic.

How do you test espresso intent?

Validate intentsUsing the intended() method, which is similar to Mockito. verify() , you can assert that a given intent has been seen. However, Espresso-Intents doesn't stub out responses to intents unless you explicitly configure it to do so. // User action that results in an external "phone" activity being launched.

How do I check if a checkbox is checked in espresso?

You can check this by: onView(withId(R. id. checkbox)).


3 Answers

PreferenceMatchers seems to work only with the preference classes from the Android framework, but not with support preference library (com.android.support:preference-v14). Since the latter uses a RecylerView internally, I was able to get hold of the preference items by using RecyclerViewActions from espresso-contrib:

onView(withId(R.id.list))
       .perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_manage_categories_title)),
            click()));
like image 173
mtotschnig Avatar answered Oct 23 '22 11:10

mtotschnig


For PreferenceFragment:

PreferenceFragment uses a ListView internally, so you can use onData() with allOf() and withTitle():

onData(allOf(is(
        instanceOf(Preference.class)),
        withTitle(R.string.my_pref_string)))
        .perform(click());

Or use onData() with allOf() and withKey():

onData(allOf(is(instanceOf(Preference.class)), withKey("myPrefKey")))
        .onChildView(withText(R.string.my_pref_string))
        .perform(click());

Or use onData() with anything(), but this can be unreliable and cause timeouts:

// atPosition() is required - Not sure why
onData(anything())
        .atPosition(3)
        .onChildView(withText(R.string.my_pref_string))
        .perform(click());

Asserting or checking for a preference item is done in a similar way:

onData(allOf(is(
        instanceOf(Preference.class)),
        withTitle(R.string.my_pref_string)))
        .check(matches(isDisplayed()));

For PreferenceFragmentCompat:

PreferenceFragmentCompat uses a RecyclerView internally, so you must use the espresso-contrib library, as mentioned in the answer by mtotschnig on 3 Septemebr 2018. If you're using the androidx.preference:preference:1.x.x library with unit tests written in Kotlin, it can be tricky to know what to use. You can start with something like this:

onView(withId(androidx.preference.R.id.recycler_view))
    .perform(actionOnItem<RecyclerView.ViewHolder>(
        hasDescendant(withText(R.string.my_pref_title)), click()))

Note: To quickly add the imports for these methods, put the blinking cursor on the unresolved method, then do Android Studio ➔ HelpFind Action ➔ search for "show context action" or "show intention action" ➔ click on the result option ➔ A popup window will appear ➔ click on "Import static method ...". You can also assign a keyboard shortcut to "Show Context Actions". More info here. Another way is to enable "Add unambiguous imports on the fly" in the Settings.

like image 34
Mr-IDE Avatar answered Oct 23 '22 11:10

Mr-IDE


After navigating to your settings activity, you can use Espresso withText() with the preference android:title String

@Rule
public ActivityTestRule<SettingsActivity> mActivityRule = new ActivityTestRule<>(
        SettingsActivity.class);

onView(withText(mActivityRule.getActivity().getResources().getString(R.string.my_pref_title))
    .perform(click());

Side note: if it is a ListPreference, then when clicking on it as mentioned above, then you can test selecting an item from the list the same way, but use the list entry text in withText() as no titles here.

onView(withText(mActivityRule.getActivity().getResources().getString(R.string.list_entry_sring))
    .perform(click());
like image 1
Zain Avatar answered Oct 23 '22 10:10

Zain