0

J'essaie de tester ma classe SettingsActivity, mais je continue d'obtenir un AmbiguousViewMatcherException.Espresso: ListViews multiples

Voici mon testcase:

@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())); 

} 

est ici la sortie du journal:

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: 
... 
+------->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} 
| 
+-------->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****** 
| 
+-------->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} 
| 
+------>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} 
| 
+------->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} 
| 
+------->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} 
| 
+-------->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} 
| 
+-------->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} 
| 
+----->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} 
| 
+------>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****** 
| 
+------->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} 
... 

Pour une raison quelconque le onData() correspond à deux ListView s, mais je ne peux pas comprendre comment empêcher cela, parce que je ne comprends pas d'où vient le premier ListView.

Voici mon preference.xml:

<?xml version="1.0" encoding="utf-8"?> 
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> 
    <PreferenceCategory 
     android:title="@string/activity_settings_sync_category_title" 
     android:key="@string/activity_settings_sync_category_key"> 

     <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"/> 

    </PreferenceCategory> 

    <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"> 


     <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" /> 

     <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" /> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_notification_ringtone_category_title" 
     android:key="@string/activity_settings_notification_ringtone_category_key"> 


     <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"/> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_dicom_category_title" 
     android:key="@string/activity_settings_dicom_category_key"> 

     <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" /> 

      <!--<EditTextPreference--> 
       <!--android:key="dcmwl_ae_title_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_ae_title"--> 
       <!--android:dialogMessage="@string/pref_dicom_settings_worklist_ip_dialog_text"--> 
       <!--android:dialogTitle="@string/pref_dicom_settings_worklist_ip_dialog_title"--> 
       <!--android:dependency="dcmwl_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:defaultValue="10.0.0.2"--> 
       <!--android:key="dcmwl_ip_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_ip"--> 
       <!--android:dependency="dcmwl_enable" />--> 
      <!--<EditTextPreference--> 
       <!--android:key="dcmwl_port_preference"--> 
       <!--android:title="@string/pref_dicom_settings_worklist_port"--> 
       <!--android:dependency="dcmwl_enable"/>--> 

     <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"/> 

      <!--<EditTextPreference--> 
       <!--android:key="pacs_ae_title_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_ae_title"--> 
       <!--android:dependency="pacs_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:defaultValue="10.0.0.2"--> 
       <!--android:key="pacs_ip_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_ip"--> 
       <!--android:dependency="pacs_enable"/>--> 
      <!--<EditTextPreference--> 
       <!--android:key="pacs_port_preference"--> 
       <!--android:title="@string/pref_dicom_settings_pacs_port"--> 
       <!--android:dependency="pacs_enable"/>--> 

    </PreferenceCategory> 

    <PreferenceCategory 
     android:title="@string/activity_settings_debug_category_title" 
     android:key="@string/activity_settings_debug_category_key"> 

     <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"/> 

    </PreferenceCategory> 

</PreferenceScreen> 

Mise à jour: tenté de modifier la solution proposée de la première réponse à ceci:

private static Matcher<View> withResName(final String resName) { 

     return new TypeSafeMatcher<View>() { 
      @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) && matchableResName.equals(resName); 
      } 
     }; 
    } 

Le test ressemble à ceci maintenant:

@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())); 


    } 

Mais ça ne marche toujours pas. La sortie du journal ressemble à ceci:

... 
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) 
... 

Répondre

0

Je suppose que PreferenceCategory va créer en interne ListView. Par conséquent, vous auriez plusieurs AdapterView s dans votre hiérarchie, donc AmbiguousViewMatcherException se produit.

Tant que vous ne pas possèdent la id de ces ListView s, vous ne pouvez pas effectuer correspondant à ViewMatcher.withId(). Ainsi, vous devez créer un matcher personnalisé basé sur quelque chose de significatif entre ceux ListView s.

Malheureusement, ceux ListView ont les mêmes attributs, donc vous devez effectuer une correspondance au niveau parent. Le parent de ces ListView s est un LinearLayout. Comme vous pouvez le remarquer, l'un d'eux (en fait celui qui ne vous intéresse pas) a un attribut res-name=headers.

Cela vous donnera une indication pour effectuer une correspondance sur un ListView, qui a un parent, dont le nom de ressource est pas« têtes ».

Essayons de le faire en langage Espresso. Firsts, Écrivons un matcher, qui correspond à une View avec un nom de ressource spécifique:

 


    class ResourceNameMatcher extends TypeSafeMatcher { 

     private final String resName; 

     public ResourceNameMatcher(String resName) { 
     this.resName = resName; 
     } 

     @Override protected boolean matchesSafely(View item) { 
     Resources resources = item.getResources(); 
     String matchableResName = resources.getResourceName(item.getId()); 
     return !TextUtils.isEmpty(matchableResName) && matchableResName.equals(resName); 
     } 

     @Override public void describeTo(Description description) { 
     description.appendText("with res-name: " + resName); 
     } 
    } 

 

Maintenant, nous allons effectuer une correspondance sur un View, qui a pas nom de ressource particulière:

 


     onData(allOf(
        Matchers.is(instanceOf(Preference.class), 
        ... // other matchers here))) 
      .onChildView(withText(expected)) 
      .inAdapterView(withParent(not(new ResourceNameMatcher("headers")))) 
      .check(matches(isDisplayed())); 

 

Remarque , c'est juste un code d'esquisse, qui peut ne pas fonctionner, tant que je ne l'ai pas testé. Néanmoins, le concept devrait fonctionner.

+0

Cela ne fonctionne pas. item.getId() ne retourne pas d'identifiant. Pour une raison quelconque, l'ID android.R.id.headers est masqué. C'est vraiment étrange parce que dans l'inspecteur de disposition, je peux voir que linearlayout a un ID nommé en-têtes et dans la sortie de débogage je peux voir la valeur entière. – Bohsen

+0

Au lieu de 'item.getId()' effectuer 'resources.getIdentifier (" headers "," id "," android ")' – azizbekian

+0

getIdentifier() faisait partie de la solution. Merci pour l'aide. – Bohsen

0

Voici ma solution pour tester mon SettingsActivity.

Le test:

@Test 
public void whenHospitalSettingEmpty_shouldDisplaySummary() throws Exception { 
    Resources res = getInstrumentation().getTargetContext().getResources(); 

    // Set the hospital preferencevalue to empty 
    mPreferenceEditor.putString(
      res.getString(R.string.activity_settings_hospital_key), 
      "").commit(); 

    // Launch activity 
    mActivityTestRule.launchActivity(null); 

    // When hospital value is empty we want to display the summary from our string ressources 
    // instead of our preferencevalue 
    onPreferenceRow(allOf(
       withKey(res.getString(R.string.activity_settings_hospital_key)), 
       withSummary(R.string.activity_settings_hospital_summary))) 
      .check(matches(isDisplayed())); 
} 

Mes méthodes d'assistance:

... 

private static Matcher<View> withResName(final String resName) { 

    return new TypeSafeMatcher<View>() { 
     @Override 
     public void describeTo(Description description) { 
      description.appendText("with res-name: " + resName); 
     } 

     @Override 
     public boolean matchesSafely(View view) { 
      int identifier = view.getResources().getIdentifier(resName, "id", "android"); 
      return !TextUtils.isEmpty(resName) && (view.getId() == identifier); 
     } 
    }; 
} 

private static DataInteraction onPreferenceRow(Matcher<? extends Object> datamatcher) { 

    DataInteraction interaction = onData(datamatcher); 

    return interaction 
     .inAdapterView(allOf(
      withId(android.R.id.list), 
      not(withParent(withResName("headers"))))); 
}