Author avatar

Pavneet Singh

Testing in Android with Espresso - Part 1 (Setup and Basics)

Pavneet Singh

  • Jul 23, 2018
  • 15 Min read
  • 22,873 Views
  • Jul 23, 2018
  • 15 Min read
  • 22,873 Views
Espresso
Android
Testing

Setup and Basics

One of the integral phases of application development life cycle is testing but the question is why?

The answer is to eliminate the occurrence of ANR dialog or unexpected behavior in application logic. For Android, all UI updation is handled by the main thread and if the main thread is unable to respond to user events within 5 seconds then it causes the application to crash and gives the user an option to close the application instead of keeping the user in an infinite waiting state, resulting in poor user experience.

Application Not Responding dialog is the least expected event that can occur when the main thread (aka UI thread) is blocked for too long. Some instances that may cause this are:

  • Rendering a larger image

  • Performing REST calls on main thread

  • Or performing any long calculation

Other reasons are Exceptions, infinite recursive method calls, missing class definitions, deadlocks ,or implementation of unsupported features on older versions like vector drawable etc.

Test Driven Development

TDD is a recursive cycle of writing code, testing, and refactoring the code to resolve bugs the moment they appear. TDD lays out the approach to test the working of the application after every modification in code base. The objectives of TDD are:

  • To meet the requirements that guided the application design and development

  • To determine the performance of an application under different environments considering

    • Network connectivity

    • User input

    • Server stability

    • Battery, CPU, and memory optimization (memory leaks)
  • To validate the application security against SQL injections and data security vulnerabilities

  • To verify the working of application specific APIs

"Testing leads to failure and failure leads to understanding :- Burt Rutan"

Unit and Instrumentation Testing

Unit Testing : Unit tests run on the local machine, i.e. Java Virtual Machine (JVM), which means it requires no device or emulator. Unit tests tend to be fast because units test don't test any Android specific dependency (Activity,SharedPreferenc) or mock objects are used to mimic the behavior of android framework dependencies.

Unit tests are located under package-name/src/test/java/

Instrumentation Testing : Instrumentation tests are specifically designed to test the UI of an application and requires an emulator or physical device to perform tests. Android UI components depend upon Context to access the resources in an application like xml, images, string, and other resources. Context is provided by the Android operating system, so for instrumentation testing the context is provided by the Instrumentation API to track the interaction between android OS and to application i.e. to give commands to activity life cycle, environment details etc.

Instrumentation tests are located under package-name/src/androidTest/java/

Testing Terminologies

  • AndroidJUnitRunner : AndroidJUnitRunner is a JUnit test runner that lets you run JUnit tests on Android devices while providing annotations to ease-up the testing procedure. For instrumentation tests, the test class must be prefixed with @RunWith(AndroidJUnit4.class).
  • Annotations : @Test Annotation is used to mark a method for testing and methods with @Before annotation are executed first to setup the environment for testing and methods with the @After annotation executed at the end. @LargeTest annotation is used to indicate that the test duration can be greater than 1 second.
  • Espresso : It is a testing framework for Android to write tests for user interface interaction.
  • Mockito : mockito is often used for unit testing to create dummy objects to mimic the behavior of objects.

Instrumentation Testing with Espresso

Espresso is a testing framework by Google for UI (user-interface) testing which includes every View component like buttons, List, Fragments etc. Espresso is collection of testing APIs, specifically designed for instrumentation/UI testing. UI tests ensure that users don’t have poor interactions or encounter unexpected behaviors.

Espresso has various options to find 'View' on screen, perform actions, and verify visibility and state/content of 'View'.

1onView(withId(R.id.my_view))       // onView() is a ViewMatcher</strong>
2  .perform(click())              // click()  is a ViewAction</strong>
3  .check(matches(isDisplayed()));// matches(isDisplayed()) is a ViewAssertion
groovy
  1. ViewMatcher : used to locate the view in the UI hierarchy(tree structure of xml layout components) using withId(R.id.id_of_view), withText("find by text on view").

  2. ViewActions : used to perform a specific action or group of actions in the UI views using ViewInteraction.perform(click(),doubleClick()) or click(), longClick(), doubleClick(), swipeDown(), swipeLeft(), swipeRight(), swipeUp(), typeText(), pressKey(), clearText(), etc.

  3. ViewAssertion : used to assert view’s state using ViewInteraction.check(assertion method) where assertion methods can be isDisplayed(), isEnabled(), isRoot().

Setup

  • Add espresso dependencies in build.gradle app module:
1dependencies {
2  implementation fileTree(dir: 'libs', include: ['*.jar'])
3
4  // AndroidJUnitRunner and JUnit Rules
5  androidTestCompile 'com.android.support.test:runner:1.0.1'
6  androidTestCompile 'com.android.support.test:rules:1.0.1'
7
8  // espresso support
9  androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
10    exclude group: 'com.android.support', module: 'support-annotations'
11  })
12  androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
13
14  // for intent mocking
15  androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.1'
16
17  // for network testing to track idle state
18  androidTestCompile 'com.android.support.test.espresso.idling:idling-concurrent:3.0.1'
19  androidTestCompile 'com.android.support.test.espresso:espresso-idling-resource:3.0.1'
20
21  // other dependencies
22}
groovy
  • To enable instrumentation testing, add the following dependency:
1testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
groovy

Complete build.gradle(app) file:

1apply plugin: 'com.android.application'
2
3android {
4  compileSdkVersion 26
5  buildToolsVersion "26.0.1"
6
7  defaultConfig {
8    applicationId "com.pavneet_singh.espressotestingdemo"
9    minSdkVersion 15
10    targetSdkVersion 26
11    versionCode 1
12    versionName "1.0"
13    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14  }
15
16  buildTypes {
17    release {
18      minifyEnabled false
19      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20    }
21  }
22}
23dependencies {
24    //as mentioned above
25}
groovy

The values for library version can differ in your project due to future updates and the rest of the configuration values are project dependent.

Turning-off Default Device Animation

The default animations can cause issues during the Espresso testing process, so it is recommended to turn-off the animation on the device and emulator before testing.

  • Open Settings

  • Select Developer Options (if not activated, then click several times on build version/number in About Device Settings)

  • Disable:

    1. Window animation scale

    2. Transition animation scale

    3. Animator duration scale

First Espresso Test with Buttons and EditText

MainActivity which contains Button and EdiTtext, on which we will perform the testing to verify visibility, entered values, and click operation.

Let's Setup MainActivity with the Following Code

activity_main.xml layout source code for MainActivity

1<?xml version="1.0" encoding="utf-8"?>
2
3<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
4    xmlns:app="http://schemas.android.com/apk/res-auto"
5    xmlns:tools="http://schemas.android.com/tools"
6    android:orientation="vertical"
7    android:layout_width="match_parent"
8    android:layout_height="match_parent"
9    tools:context="com.pavneet_singh.MainActivity">
10
11    <EditText
12        android:layout_marginTop="30dp"
13        android:id="@+id/editTextName"
14        android:hint="Enter Name"
15        android:layout_width="match_parent"
16        android:layout_height="wrap_content"
17        android:ems="10"
18        android:inputType="textPersonName" />
19
20    <Button
21        android:onClick="setDefaultText"
22        android:layout_marginTop="60dp"
23        android:id="@+id/button"
24        android:layout_width="match_parent"
25        android:layout_height="wrap_content"
26        android:text="Clear" />
27
28    <android.support.design.widget.FloatingActionButton
29        android:id="@+id/fab"
30        android:layout_width="wrap_content"
31        android:layout_height="wrap_content"
32        android:layout_gravity="bottom|end"
33        android:layout_margin="@dimen/fab_margin"
34        app:srcCompat="@android:drawable/ic_dialog_email" />
35</LinearLayout>
xml

The onClick attribute in xml points to a public method to handle click events in MainActivity as shown below:

1package com.pavneet_singh;
2import android.content.Intent;
3import android.os.Bundle;
4import android.support.design.widget.FloatingActionButton;
5import android.support.design.widget.Snackbar;
6import android.support.v7.app.AppCompatActivity;
7import android.support.v7.widget.Toolbar;
8import android.view.View;
9import android.widget.EditText;
10import com.pavneet_singh.espressotestingdemo.FruitListActivity;
11import com.pavneet_singh.espressotestingdemo.R;
12
13public class MainActivity extends AppCompatActivity {
14  private EditText editTextName;
15
16  @Override
17  protected void onCreate(Bundle savedInstanceState) {
18    super.onCreate(savedInstanceState);
19
20    setContentView(R.layout.activity_main);
21
22    editTextName = (EditText)findViewById(R.id.editTextName);
23
24    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
25
26    fab.setOnClickListener(new View.OnClickListener() {
27      @Override
28      public void onClick(View view) {
29        startActivity(new Intent(MainActivity.this, FruitListActivity.class));
30      }
31    });
32  }
33
34  public void setDefaultText(View view) {
35    editTextName.setText("");
36  }
37}
java

Writing an Espresso Test

To Add a test class corresponding to MainActivity

  1. Open the Java file containing the code you want to test.

  2. Click the class or method you want to test, then press Ctrl+Shift+T.

  3. In the menu that appears, click Create New Test.

  4. In the Create Test dialog, edit any fields and select any methods to generate, then click OK.

  5. In the Choose Destination Directory dialog i.e. androidTest for an instrumented test click OK.

Note : Generating your test class in the same package of the original class which is being tested, will give you access to everything inside that class and package without making them public.

Add the following code into MainActivityTest class:

1package com.pavneet_singh;
2// Note the static imports, which enhance the code clarity by reducing code length
3import static android.support.test.espresso.Espresso.onView;
4import static android.support.test.espresso.action.ViewActions.click;
5import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
6import static android.support.test.espresso.action.ViewActions.typeText;
7import static android.support.test.espresso.matcher.ViewMatchers.withHint;
8import static android.support.test.espresso.matcher.ViewMatchers.withId;
9import static android.support.test.espresso.assertion.ViewAssertions.matches;
10import static android.support.test.espresso.matcher.ViewMatchers.withText;
11import android.support.test.espresso.ViewAssertion;
12import android.support.test.espresso.action.ViewActions;
13import android.support.test.filters.LargeTest;
14import android.support.test.rule.ActivityTestRule;
15import android.support.test.runner.AndroidJUnit4;
16import org.junit.Rule;
17import org.junit.Test;
18import org.junit.runner.RunWith;
19import com.pavneet_singh.espressotestingdemo.R;
20
21@RunWith(AndroidJUnit4.class)
22@LargeTest
23
24public class MainActivityTest {
25  // To launch the mentioned activity under testing
26  @Rule
27  public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
28
29  @Test
30  public void testHintVisibility(){
31      // check hint visibility
32      onView(withId(R.id.editTextName)).check(matches(withHint("Enter Name")));
33      // enter name
34      onView(withId(R.id.editTextName)).perform(typeText("Pavneet"),closeSoftKeyboard());
35      onView(withId(R.id.editTextName)).check(matches(withText("Pavneet")));
36  }
37
38  @Test
39  public void testButtonClick(){
40      // enter name`
41      onView(withId(R.id.editTextName)).perform(typeText("Pavneet"),closeSoftKeyboard());
42      // clear text
43      onView(withText("Clear")).perform(click());
44      // check hint visibility after the text is cleared
45      onView(withId(R.id.editTextName)).check(matches(withHint("Enter Name")));
46  }
47}
java

To Run Test:

  • Select the MainActivityTest under package-name/androidTest.

  • Right click on the file.

  • Select run MainActivityTest.

Or via shortcuts:

  • Windows : shift + F10

  • Mac : Control + R

onView also has an overloaded version which can accept powerful hamcrest Matcher methods to go one step beyond and perform specific operations like:

  • AllOf : To perform multiple operations together.
1onView(AllOf.allOf(withId(R.id.editTextName),withId(R.id.editTextSameName))).check(matches(withText("Pavneet")));
java
  • StringContains : To match a part of the input string in the targeted view text.
1onView(withId(R.id.editTextName)).check(matches(withText(Matchers.containsString("Pavneet"))));
java
1onView(withText(startsWith("prefix"))).perform(click());
java

References

Read Test Cases with Espresso to explore more about testing various android components and view.


I hope that testing will now help you to be a stellar programmer, you can share your love by giving this guide a thumbs up.