Android - эспрессо - нажатие на запись listview на основе пользовательских объектов

Espresso используется для автоматического тестирования моего приложения.

Изменить: ниже вы найдете несколько ответов!

Как я могу щелкнуть (в рамках автоматического теста Espresso script) на запись в длинном списке пользовательских объектов?

В документации Espresso есть пример LongList. Работа со списком объектов - это то, что я обычно делаю. Попытка множества вариантов перехода от карты к объекту пока не принесла хороших результатов.

В документации Espresso говорится, что необходимо использовать 'onData'. Итак, что-то вроде:

onData( myObjectHasContent("my_item: 50")).perform(click());
onView(withId( R.id.selection_pos2)).check(matches(withText("50")));

Мои вопросы (и я думаю, что они полезны для сообщества обучения):  - Можете ли вы написать для этого хорошего Матчи?  - Как мы можем использовать это в 'onData'?

Какая ситуация? На экране у меня есть список объектов вроде:

public class MyOjbect { 
    public String content; 
    public int    size; 
}

Адаптер, который я использую для заполнения заполненного списка:

public class MyObjectWithItemAndSizeAdapter extends ArrayAdapter<MyObjectWithItemAndSize> {
    private final Context context;
    private final List<MyObjectWithItemAndSize> values;
    ...
    @Override
    public View getView(int position, View concertView, ViewGroup parent) {
        View view = null;
        if (concertView != null) {
            view = (LinearLayout) concertView;
        } else {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate( R.layout.list_item, parent, false);
        } 
        TextView itemT = (TextView) view.findViewById( R.id.item_content);
        itemT.setText( values.get(position).item);
        TextView sizeT = (TextView) view.findViewById( R.id.item_size);
        sizeT.setText( "" + values.get(position).size);
        return view;
    }
 }

Ответ 1

В дополнение к предыдущему ответу, я создаю полную версию с двумя типами проверок. Это может помочь вам понять Espresso и Custom Matchers.

Разница со стандартным примером Espresso LongList заключается в том, что я использую список пользовательских объектов для отображения в списке. Прокрутка к списку справа и проверка результата показана ниже.

Способ 1 - проверка на строку

В тесте script есть:

onData( allOf( instanceOf( MyObjectWithItemAndSize.class), myCustomObjectShouldHaveString( "my_item: 60")))
         .perform(click());
// testing the result ... as in the longlist example
onView(withId(R.id.selection_pos2)).check(matches(withText("my_item: 60"))); 

Соединитель:

public static Matcher<Object> myCustomObjectShouldHaveString( String expectedTest) {
    return myCustomObjectShouldHaveString( equalTo( expectedTest));
}
private static Matcher<Object> myCustomObjectShouldHaveString(final Matcher<String> expectedObject) {
return new BoundedMatcher<Object, MyObjectWithItemAndSize>( MyObjectWithItemAndSize.class) {
    @Override
    public boolean matchesSafely(final MyObjectWithItemAndSize actualObject) {
        // next line is important ... requiring a String having an "equals" method
        if( expectedObject.matches( actualObject.item) ) {
             return true;
           } else { 
             return false;
           }
      }
      @Override
      public void describeTo(final Description description) {
         // could be improved, of course
         description.appendText("getnumber should return ");
      }
   };
}

Способ 2: проверка на (полный объект).

В тесте script вы найдете:

MyObjectWithItemAndSize myObject = new MyObjectWithItemAndSize( "my_item: 60", 11);
onData( allOf( instanceOf( MyObjectWithItemAndSize.class), myObjectHasContent( myObject))).perform( click());
onView(withId( R.id.selection_pos2)).check(matches(withText("my_item: 60"))); 

Соединитель.

Самая важная строка (с которой я боролся) ниже // ****

public static Matcher<Object> myObjectHasContent( MyObjectWithItemAndSize expectedObject) {
   return myObjectHasContent( equalTo( expectedObject));
}
//private method that does the work of matching
private static Matcher<Object> myObjectHasContent(final Matcher<MyObjectWithItemAndSize> expectedObject) {
     return new BoundedMatcher<Object, MyObjectWithItemAndSize>(MyObjectWithItemAndSize.class) {
        @Override
        public boolean matchesSafely( final MyObjectWithItemAndSize actualObject) {
            // ****** ... the 'matches'. See below. 
            // this requires the MyObjectWithItemAndSize to have an 'equals' method
            if( expectedObject.matches( actualObject) ) {
                return true;
            } else { 
                return false;
            }
        }
        @Override
        public void describeTo(final Description description) {
           description.appendText("getnumber should return ");
        }
     };
  }

Что очень важно, так это то, что пользовательский объект имеет этот метод (и я думаю, переопределяю):

@Override
public boolean equals( Object mob2) {
    return( (this.item.equals( ((MyObjectWithItemAndSize) mob2).item)));
    // of course, could have also a check on this.size.
} 

И это работает!!!! Pfff, потребовалось некоторое время, но победил. Спасибо также Yash F.

Ответ 2

Соответствующий символ onData() должен соответствовать желаемому значению, возвращаемому Adapter.getItem(int) желаемого ListView.

Итак, в вашем примере совпадение должно быть примерно таким:

public static Matcher<Object> withContent(final String content) {
    return new BoundedMatcher<Object, MyObjectWithItemAndSize>(MyObjectWithItemAndSize.class) {
        @Override
        public boolean matchesSafely(MyObjectWithItemAndSize myObj) {
            return myObj.content.equals(content);
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("with content '" + content + "'");
        }
    };
}

Ответ 3

Мне нужно было протестировать и AdapterView с помощью пользовательского адаптера, используя Espresso 2 сегодня. Я закончил использование FeatureMatcher:

private static FeatureMatcher<Product, String> withProductName(final String productName) {
    return new FeatureMatcher<Product, String>(equalTo(productName), "with productName", "productName") {
        @Override
        protected String featureValueOf(Product actual) {
            return actual.name;
        }
    };
}

И затем вызов этого метода утилиты из теста, например:

onData(withProductName("My Awesome Product"))
            .inAdapterView(withId(R.id.product_list))
            .onChildView(withId(R.id.product_title))
            .check(matches(withText("My Awesome Product")));

Я думаю, что FeatureMatcher отлично работает, когда вы хотите утверждать конкретное свойство объекта данных.