Espresso Testing Framework - AdapterView



AdapterView is a special kind of view specifically designed to render a collection of similar information like product list and user contacts fetched from an underlying data source using Adapter. The data source may be simple list to complex database entries. Some of the view derived from AdapterView are ListView, GridView and Spinner.

AdapterView renders the user interface dynamically depending on the amount of data available in the underlying data source. In addition, AdapterView renders only the minimum necessary data, which can be rendered in the available visible area of the screen. AdapterView does this to conserve memory and to make the user interface look smooth even if the underlying data is large.

Upon analysis, the nature of the AdapterView architecture makes the onView option and its view matchers irrelevant because the particular view to be tested may not be rendered at all in the first place. Luckily, espresso provides a method, onData(), which accepts hamcrest matchers (relevant to the data type of the underlying data) to match the underlying data and returns object of type DataInteraction corresponding to the view o the matched data. A sample code is as follows,

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

Here, onData() matches entry “Apple”, if it is available in the underlying data (array list) and returns DataInteraction object to interact with the matched view (TextView corresponding to “Apple” entry).

Methods

DataInteraction provides the below methods to interact with the view,

perform()

This accepts view actions and fires the passed in view actions.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

check()

This accepts view assertions and checks the passed in view assertions.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .check(matches(withText("Apple")))

inAdapterView()

This accepts view matchers. It selects the particular AdapterView based on the passed in view matchers and returns DataInteraction object to interact with the matched AdapterView

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

atPosition()

This accepts an argument of type integer and refers the position of the item in the underlying data. It selects the view corresponding to the passed in positional value of the data and returns DataInteraction object to interact with the matched view. It will be useful, if we know the correct order of the underlying data.

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

onChildView()

This accepts view matchers and matches the view inside the specific child view. For example, we can interact with specific items like Buy button in a product list based AdapterView.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .onChildView(withId(R.id.buy_button))
   .perform(click())

Write a Sample Application

Follow the steps shown below to write a simple application based on AdapterView and write a test case using the onData() method.

  • Start Android studio.

  • Create new project as discussed earlier and name it, MyFruitApp.

  • Migrate the application to AndroidX framework using RefactorMigrate to AndroidX option menu.

  • Remove default design in the main activity and add ListView. The content of the activity_main.xml is as follows,

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Add new layout resource, item.xml to specify the item template of the list view. The content of the item.xml is as follows,

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Now, create an adapter having fruit array as underlying data and set it to the list view. This needs to be done in the onCreate() of MainActivity as specified below,

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   
   // Find fruit list view
   final ListView listView = (ListView) findViewById(R.id.listView);
   
   // Initialize fruit data
   String[] fruits = new String[]{
      "Apple", 
      "Banana", 
      "Cherry", 
      "Dates", 
      "Elderberry", 
      "Fig", 
      "Grapes", 
      "Grapefruit", 
      "Guava",
      "Jack fruit", 
      "Lemon",
      "Mango", 
      "Orange", 
      "Papaya", 
      "Pears", 
      "Peaches", 
      "Pineapple",
      "Plums", 
      "Raspberry",
      "Strawberry", 
      "Watermelon"
   };
   
   // Create array list of fruits
   final ArrayList<String> fruitList = new ArrayList<String>();
   for (int i = 0; i < fruits.length; ++i) {
      fruitList.add(fruits[i]);
   }
   
   // Create Array adapter
   final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, fruitList);
   
   // Set adapter in list view
   listView.setAdapter(adapter);
}
  • Now, compile the code and run the application. The screenshot of the My Fruit App is as follows,

Compile The Code
  • Now, open ExampleInstrumentedTest.java file and add ActivityTestRule as specified below,

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

Also, make sure the test configuration is done in app/build.gradle

dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Add a new test case to test the list view as below,

@Test
public void listView_isCorrect() {
   // check list view is visible
   onView(withId(R.id.listView)).check(matches(isDisplayed()));
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click());
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
      .check(matches(withText("Apple")));
   // click a child item
   onData(allOf())
      .inAdapterView(withId(R.id.listView))
      .atPosition(10)
      .perform(click());
}
  • Finally, run the test case using android studio’s context menu and check whether all test cases are succeeding.

Advertisements