title: Android Espresso: Testing Preferences (multiple ListViews)
body: <p>I'm trying to test my <code>SettingsActivity</code> class, but I keep getting a <code>AmbiguousViewMatcherException</code>.</p>
  
  <p>Here's my testcase:</p>
  
  <pre><code>@Test
  public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception {
      Resources res = getInstrumentation().getTargetContext().getResources();
  
      mPreferenceEditor.putString(
              res.getString(R.string.activity_settings_hospital_key),
              "");
  
      mActivityTestRule.launchActivity(null);
  
      String expected = res.getString(R.string.activity_settings_hospital_summary);
  
      onData(allOf(
              is(instanceOf(Preference.class)),
              withKey(res.getString(R.string.activity_settings_hospital_key)),
              withSummary(R.string.activity_settings_hospital_summary),
              withTitle(R.string.activity_settings_hospital_title)))
              .onChildView(withText(expected))
              .check(matches(isDisplayed()));
  
  }
  </code></pre>
  
  <p>Here's the log output:</p>
  
  <pre><code>android.support.test.espresso.AmbiguousViewMatcherException: 'is assignable from class: class android.widget.AdapterView' matches multiple views in the hierarchy.
  Problem views are marked with '****MATCHES****' below.
  
  View Hierarchy:
  ...
  +-------&gt;LinearLayout{id=16909142, res-name=headers, visibility=VISIBLE, width=600, height=887, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2}
  |
  +--------&gt;ListView{id=16908298, res-name=list, visibility=VISIBLE, width=536, height=823, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=32.0, y=32.0, child-count=0} ****MATCHES******
  |
  +--------&gt;FrameLayout{id=16909143, res-name=list_footer, visibility=VISIBLE, width=536, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=32.0, y=855.0, child-count=0}
  |
  +------&gt;RelativeLayout{id=16909146, res-name=button_bar, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2}
  |
  +-------&gt;AppCompatButton{id=16909147, res-name=back_button, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Tilbage, input-type=0, ime-target=false, has-links=false}
  |
  +-------&gt;LinearLayout{id=-1, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2}
  |
  +--------&gt;AppCompatButton{id=16909148, res-name=skip_button, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Spring over, input-type=0, ime-target=false, has-links=false}
  |
  +--------&gt;AppCompatButton{id=16909149, res-name=next_button, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Næste, input-type=0, ime-target=false, has-links=false}
  |
  +-----&gt;LinearLayout{id=-1, visibility=VISIBLE, width=600, height=887, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=3}
  |
  +------&gt;ListView{id=16908298, res-name=list, visibility=VISIBLE, width=600, height=887, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=12} ****MATCHES******
  |
  +-------&gt;AppCompatTextView{id=16908310, res-name=title, visibility=VISIBLE, width=584, height=35, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=8.0, y=0.0, text=Synkronisering, input-type=0, ime-target=false, has-links=false}
  ...
  </code></pre>
  
  <p>For some reason the <code>onData()</code> matches two <code>ListView</code>s, but I just can't figure out how to prevent this, because I don't understand where the first <code>ListView</code> comes from.</p>
  
  <p>Here's my <code>preference.xml</code>:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"&gt;
      &lt;PreferenceCategory
          android:title="@string/activity_settings_sync_category_title"
          android:key="@string/activity_settings_sync_category_key"&gt;
  
          &lt;SwitchPreference
              android:key="@string/activity_settings_sync_key"
              android:summary="@string/activity_settings_sync_summary"
              android:title="@string/activity_settings_sync_title"
              android:defaultValue="true"/&gt;
  
      &lt;/PreferenceCategory&gt;
  
      &lt;PreferenceCategory
          android:title="@string/activity_settings_site_category_title"
          android:key="@string/activity_settings_site_category_key"
          android:summary="@string/activity_settings_site_category_summary"&gt;
  
  
          &lt;EditTextPreference
              android:key="@string/activity_settings_hospital_key"
              android:title="@string/activity_settings_hospital_title"
              android:summary="@string/activity_settings_hospital_summary"
              android:dialogTitle="@string/activity_settings_hospital_dialog_title"
              android:dialogMessage="@string/activity_settings_hospital_dialog_message"
              android:inputType="textCapWords" /&gt;
  
          &lt;EditTextPreference
              android:key="@string/activity_settings_department_key"
              android:title="@string/activity_settings_department_title"
              android:summary="@string/activity_settings_department_summary"
              android:dialogTitle="@string/activity_settings_department_dialog_title"
              android:dialogMessage="@string/activity_settings_department_dialog_message"
              android:inputType="textCapWords" /&gt;
  
      &lt;/PreferenceCategory&gt;
  
      &lt;PreferenceCategory
          android:title="@string/activity_settings_notification_ringtone_category_title"
          android:key="@string/activity_settings_notification_ringtone_category_key"&gt;
  
  
          &lt;RingtonePreference
              android:key="@string/activity_settings_notification_ringtone_key"
              android:title="@string/activity_settings_notification_ringtone_title"
              android:summary="@string/activity_settings_notification_ringtone_summary"
              android:dialogTitle="@string/activity_settings_notification_ringtone_dialog_title"
              android:dialogMessage="@string/activity_settings_notification_ringtone_dialog_message"/&gt;
  
      &lt;/PreferenceCategory&gt;
  
      &lt;PreferenceCategory
          android:title="@string/activity_settings_dicom_category_title"
          android:key="@string/activity_settings_dicom_category_key"&gt;
  
          &lt;SwitchPreference
                  android:key="@string/activity_settings_dicom_worklist_key"
                  android:title="@string/activity_settings_dicom_worklist_title"
                  android:summary="@string/activity_settings_dicom_worklist_summary" /&gt;
  
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:key="dcmwl_ae_title_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_worklist_ae_title"--&gt;
                  &lt;!--android:dialogMessage="@string/pref_dicom_settings_worklist_ip_dialog_text"--&gt;
                  &lt;!--android:dialogTitle="@string/pref_dicom_settings_worklist_ip_dialog_title"--&gt;
                  &lt;!--android:dependency="dcmwl_enable"/&gt;--&gt;
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:defaultValue="10.0.0.2"--&gt;
                  &lt;!--android:key="dcmwl_ip_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_worklist_ip"--&gt;
                  &lt;!--android:dependency="dcmwl_enable" /&gt;--&gt;
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:key="dcmwl_port_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_worklist_port"--&gt;
                  &lt;!--android:dependency="dcmwl_enable"/&gt;--&gt;
  
          &lt;SwitchPreference
              android:key="@string/activity_settings_dicom_pacs_key"
              android:title="@string/activity_settings_dicom_pacs_title"
              android:summary="@string/activity_settings_dicom_pacs_summary"/&gt;
  
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:key="pacs_ae_title_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_pacs_ae_title"--&gt;
                  &lt;!--android:dependency="pacs_enable"/&gt;--&gt;
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:defaultValue="10.0.0.2"--&gt;
                  &lt;!--android:key="pacs_ip_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_pacs_ip"--&gt;
                  &lt;!--android:dependency="pacs_enable"/&gt;--&gt;
              &lt;!--&lt;EditTextPreference--&gt;
                  &lt;!--android:key="pacs_port_preference"--&gt;
                  &lt;!--android:title="@string/pref_dicom_settings_pacs_port"--&gt;
                  &lt;!--android:dependency="pacs_enable"/&gt;--&gt;
  
      &lt;/PreferenceCategory&gt;
  
      &lt;PreferenceCategory
          android:title="@string/activity_settings_debug_category_title"
          android:key="@string/activity_settings_debug_category_key"&gt;
  
          &lt;SwitchPreference
              android:key="@string/activity_settings_debug_key"
              android:summary="@string/activity_settings_debug_summary"
              android:title="@string/activity_settings_debug_title"
              android:defaultValue="false"/&gt;
  
      &lt;/PreferenceCategory&gt;
  
  &lt;/PreferenceScreen&gt;
  </code></pre>
  
  <p><strong>Update:</strong>
  Tried to modify the proposed solution from first answer like this:</p>
  
  <pre><code>private static Matcher&lt;View&gt; withResName(final String resName) {
  
          return new TypeSafeMatcher&lt;View&gt;() {
              @Override
              public void describeTo(Description description) {
                  description.appendText("with res-name: " + resName);
                  Timber.d("Resourcename: ", resName);
              }
  
              @Override
              public boolean matchesSafely(View view) {
                  Timber.d("View ID: ", view.getId());
                  String matchableResName = view.getResources().getResourceEntryName(view.getId());
                  return !TextUtils.isEmpty(matchableResName) &amp;&amp; matchableResName.equals(resName);
              }
          };
      }
  </code></pre>
  
  <p>The test looks like this now:</p>
  
  <pre><code>@Test
      public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception {
          Resources res = getInstrumentation().getTargetContext().getResources();
  
          mPreferenceEditor.putString(
                  res.getString(R.string.activity_settings_hospital_key),
                  "").commit();
  
          mActivityTestRule.launchActivity(null);
  
          String expected = res.getString(R.string.activity_settings_hospital_summary);
  
  //        onView(withText(expected)).check(matches(isDisplayed()));
  
          onData(allOf(
                  is(instanceOf(Preference.class)),
                  withKey(res.getString(R.string.activity_settings_hospital_key)),
                  withSummary(R.string.activity_settings_hospital_summary),
                  withTitle(R.string.activity_settings_hospital_title)))
                  .onChildView(withText(expected))
                  .inAdapterView(withParent(not(withResName("headers"))))
                  .check(matches(isDisplayed()));
  
  
      }
  </code></pre>
  
  <p>But it still doesn't work. The log output looks like this:</p>
  
  <pre><code>...
  I/TestRunner: android.content.res.Resources$NotFoundException:
   Unable to find resource ID #0xffffffff
      at android.content.res.Resources.getResourceEntryName(Resources.java:2127)
      at com.conhea.smartgfr.preferences.SettingsActivityTest$2.matchesSafely(SettingsActivityTest.java:153)
      at com.conhea.smartgfr.preferences.SettingsActivityTest$2.matchesSafely(SettingsActivityTest.java:143)
      at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
      at org.hamcrest.core.IsNot.matches(IsNot.java:25)
      at android.support.test.espresso.matcher.ViewMatchers$26.matchesSafely(ViewMatchers.java:892)
      at android.support.test.espresso.matcher.ViewMatchers$26.matchesSafely(ViewMatchers.java:883)
      at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
  ...
  </code></pre>