Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - espresso - clicking on a listview entry based on custom objects

Espresso is used for automatic testing my App.

Edit: below you find a number of answers!

How can I click (within an automated Espresso test script) on an entry in a long list of custom objects?

In the Espresso documentation there is an example of a LongList. Working with a List of objects is what I normally do. Trying many options to step from Map to Object didn't yield good results so far.

The Espresso documentation says a 'onData' should be used. So, something like:

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

My questions (and I think they are helpful for the learning community): - Can you write a good Matcher for this? - How can we use that in the 'onData' ?

What is the situation? On the screen I have a listview of objects like:

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

The adapter I use to populate the populated list is:

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;
    }
 }
like image 853
tm1701 Avatar asked Aug 18 '14 07:08

tm1701


2 Answers

I needed to test an AdapterView with a custom adapter using Espresso 2 today. I ended up using 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;
        }
    };
}

And then calling this utility method from a test like follows:

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

I think FeatureMatcher is great when you want to assert a specific property of a data object.

like image 151
argenkiwi Avatar answered Sep 27 '22 18:09

argenkiwi


An addition to the previous answer, I create a complete version with 2 kind of validations. This may help you understand Espresso and Custom Matchers.

The difference with the standard Espresso LongList example is that I use a list of Custom objects to show in a listview. Scrolling to the right list-entry and checking the result is shown below.

Method 1 - the validation against a string

In the test script is:

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

The matcher is:

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 ");
      }
   };
}

Method 2: validating against the (complete object).

In the test script you find:

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

The matcher is.

The most important line (I have been struggling with) is below // ****

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 ");
        }
     };
  }

What is very important is that the Custom object has this method (and I guess overriden):

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

And it works!!!! Pfff, took a while, but overcame. Thanks also to Yash F.

like image 23
tm1701 Avatar answered Sep 27 '22 20:09

tm1701