Sep 22, 2015

Espresso Testing On Android

Espresso Testing On Android

Android apps fail for a number of reasons other than simple logic errors. At its most basic the app may not install correctly, or there may be a problem when you move from landscape to portrait and back again. Because of fragmentation the layout might not work on any number of devices that you haven’t had the time to test it on or it could hang if the network is down.

It’s just not possible to test for these conditions using unit testing. We’re going to have to use another testing tool to test our GUIs or activities. And unfortunately it also means we’re back to using devices and emulators to do our testing again.

There are lots of options out there, such as UIAutomator, Calabash, Robotium and Selenium. Until recently I’ve been using Calabash because of its Given/When/Then writing format which works great with business users. However there are significant advantages to using Espresso which are too hard to resist.

All of these other products are third party products whereas Espresso is a Google first party product. Normally this wouldn’t be any sort of advantage but because of Espresso’s ability to hook into the Android lifecycle it does a wonderful job of knowing exactly when the activity is ready to perform your tests. GUI tests in Android are typically full of sleep() commands to ensure that the Activity is ready to accept your data. With Espresso there is simply no need for any waiting or sleeping, it just fires the test when the app is ready to accept the input data. This synchronization between the UI thread and Espresso means that tests run much more reliably than with the other tools. If a test fails then it’s because there’s an error in your code rather than you need to add more time to the sleep command.

onView

While we already looked at the Espresso in in our earlier blog Android Testing it makes sense to go back to basics and do a real Hello World Espresso test.

In the last post we showed how to setup the Espresso environment as follows

  • Prerequisites – Intall the Android Support Repository

  • Test classes are in the src/androidTest/java folders

  • Add Espresso dependency in build.gradle (app) file

  • Choose Android Test Instrumentation Test Artifact in Build Variant

  • Create GUI tests

  • Right click on tests to run tests

Instead of jUnit or Hamcrest assertions Espresso uses the OnView format which uses a ViewMatcher to find the element in the activity we’re testing, ViewAction performs the action e.g. click, and matches is the assertion which makes sure the text matches and the test passes.123

onView(ViewMatcher)           
  .perform(ViewAction)   
  .check(ViewAssertion);

Listing 1 shows the code for the standard Android Hello World app

public class MainActivity extends Activity {
    private TextView mLabel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Listing 1. Hello World

Figure 1 shows the app running on the emulator. Our simple Espresso test is going to find the text and make sure it’s really saying Hello world!

Figure 1. Hello World!

The code for the simple test is in Listing 2. The test is annotated as a @LargeTest because we need the emulator to run Espresso tests. We’re using a jUnit4 rule to launch the Main Activity, see the @Rule annotation.

Once we have access to the Activity we use the onView code to find our Hello World text and a .check to see if the text is what it was defined as in the strings.xml file. In this case there is no need for the perform step so that is omitted.

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityTest {
  @Rule
  public ActivityTestRule<MainActivity> activityTestRule
    = new ActivityTestRule<> (MainActivity.class);
  @Test
  public void helloWorldTest() {
    onView(withId(R.id.hello_world))
      .check(matches(withText(R.string.hello_world)));
  }
}

Listing 2. Hello World Espresso test

The test passes and the results are shown in Android Studio similar to the unit test output, see Figure 2.

Figure 2. Hello World Espresso test results

Adding Buttons

Next let’s add a button to our Hello World code. We do this by adding the following code to our activity_main.xml file, see Listing 3. The button_label string will also need to be added to the strings.xml file. Note that the button is enabled by default.12

<button android_id="@+id/button" android:text="@string/button_label"android:layout_width="wrap_content" android:layout_height="wrap_content">
</button>

Listing 3. Adding Hello World button

Figure 3 shows our modified app with the new button.

Figure 3. Hello World with button

We want to make sure the button is on or enabled. The test code is now shown in Listing 4. This time we’re using the perform action to click the button.

@Test
public void helloWorldButtonTest(){
  onView(withId(R.id.button))
    .perform(click())
    .check(matches(isEnabled()));
}

Listing 4. onView button test

The test successfully runs as everything is green, see Figure 4.

Figure 4. Hello World test results

Table 1. ViewMatcher

ViewActions

Table 2 shows the available ViewAction options.

Table 2. ViewAction

ViewAssertions

Table 3 shows the available ViewAssertion options.

Table 3. ViewAssertion

OnData

onView won’t be able to find the data when we’re using any AdapterViews such as ListView, Gridview or Spinner. For AdapterViews we have to use onData in conjunction with the onView to locate and test the item as follows.

The OnData format is as follows:

onData(ObjectMatcher)  
  .DataOptions
  .perform(ViewAction)
  .check(ViewAssertion)

The DataOptions available are inAdapterView, atPosition or onChildView.

To Do List

To see how this works lets look at how to test To Do List application which has a ListView adapter, see Figure 5.

Figure 5. ToDoList application

Our application uses a ListView Adapter. The code can be found in Listing 5.

public class MainActivity extends Activity {
    private TextView mtxtSelectedItem;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mtxtSelectedItem = (TextView) findViewById(R.id.txt_selected_item);
        String[] todolist = {String[] todolist = {"pick up the kids","pay bills","do laundry","buy groceries ","go the gym","clean room","call mum"};
        List<String> list = Arrays.asList(todolist);
        ArrayAdapter<String> adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
        ListView listView = (ListView) findViewById(R.id.list_of_todos);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String text = ((TextView) view).getText().toString();
                Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
                mtxtSelectedItem.setText(text);
            }
        });
    }
 }

Listing 5. To Do List code

A simple test to make sure everything is working ok would be to pick something on the To Do List, such as “go to the gym”. The code to is shown in Listing 6. We’re telling the test to look at position [4] in the AdapterView in the onData code and then passing that to the onView so that it can check that the text does indeed say “go to the gym”

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityTest {
    @Rule
    public ActivityTestRule<MainActivity> activityTestRule
      = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void toDoListTest(){
      onData(anything())
        .inAdapterView(withId(R.id.list_of_todos)).atPosition(4)
        .perform(click());
      onView(withId(R.id.txt_selected_item))
        .check(matches(withText("go to the gym")));
    }
}

Listing 6. onData test code

Run the test once again using the emulator or on a device.

Espresso Tests in Jenkins

To run the Espresso tests in Jenkins, click on Add Build Step->Invoke Gradle Script and add the connectedCheck task, see figure 6.

Figure 6. Adding Espresso tests in Jenkins

Espresso needs an emulator to perform its tests, so you also need to install the Android Emulator Plugin. You can choose to let Jenkins use an existing emulator or create a new one, see Figure 7.

Listing 7. Using an existing emulator

Summary

In this blog we’ve looked at a number of Espresso tests using both onView and onData. Finally if you’re wondering how many Espresso tests we should have in our suite of tests, then go back to our Agile testing pyramid in our earlier blog Android Testing and you should see we should always have a lot more unit tests than Espresso tests.

Note a complete set of these testing blogs have been compiled together into an Agile Android mini-book, published by Apress and available from AmazonThis is Part 4 of 6, the remaining blogs can be found below.

Part 1 – Agile Testing on Android

Part 2 – Android Unit Testing

Part 3 – Hamcrest, JaCoCo, Mockito, and Jenkins for Android

Part 4 – Espresso Testing on Android

Part 5 – Android Mocking

Part 6 – Maintaining Someone Else’s Android Code