Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Testing Android User Interfaces With Espresso

$
0
0
tag:code.tutsplus.com,2005:PostPresenter/cms-31687

In this post, you'll learn about how to write UI tests with the Espresso testing framework and automate your test workflow, instead of using the tedious and highly error-prone manual process. 

Espresso is a testing framework for writing UI tests in Android. According to the official docs, you can:

Use Espresso to write concise, beautiful, and reliable Android UI tests.

1. Why Use Espresso?

One of the problems with manual testing is that it can be time-consuming and tedious to perform. For example, to test a login screen (manually) in an Android app, you will have to do the following:

  1. Launch the app. 
  2. Navigate to the login screen. 
  3. Confirm if the usernameEditText and passwordEditText are visible. 
  4. Type the username and password into their respective fields. 
  5. Confirm if the login button is also visible, and then click on that login button.
  6. Check if the correct views are displayed when that login was successful or was a failure. 

Instead of spending all this time manually testing our app, it would be better to spend more time writing code that makes our app stand out from the rest! And, even though manual testing is tedious and quite slow, it is still error-prone and you might miss some corner cases. 

Some of the advantages of automated testing include the following:   

  • Automated tests execute exactly the same test cases every time they are executed. 
  • Developers can quickly spot a problem quickly before it is sent to the QA team. 
  • It can save a lot of time, unlike doing manual testing. By saving time, software engineers and the QA team can instead spend more time on challenging and rewarding tasks. 
  • Higher test coverage is achieved, which leads to a better quality application. 

In this tutorial, we'll learn about Espresso by integrating it into an Android Studio project. We'll write UI tests for a login screen and a RecyclerView, and we'll learn about testing intents. 

Quality is not an act, it is a habit. — Pablo Picasso

2. Prerequisites

To be able to follow this tutorial, you'll need:

A sample project (in Kotlin) for this tutorial can be found on our GitHub repo so you can easily follow along.

3. Create an Android Studio Project

Fire up your Android Studio 3 and create a new project with an empty activity called MainActivity. Make sure to check Include Kotlin support

Create Android Project dialog

4. Set Up Espresso and AndroidJUnitRunner

After creating a new project, make sure to add the following dependencies from the Android Testing Support Library in your build.gradle (although Android Studio has already included them for us). In this tutorial, we are using the latest Espresso library version 3.0.2 (as of this writing). 

We also included the instrumentation runner AndroidJUnitRunner:

An Instrumentation that runs JUnit3 and JUnit4 tests against an Android package (application).

Note that Instrumentation is simply a base class for implementing application instrumentation code. 

Turn Off Animation 

The synchronisation of Espresso, which doesn't know how to wait for an animation to finish, can cause some tests to fail—if you allow animation on your test device. To turn off animation on your test device, go to Settings > Developer Options and turn off all the following options under the "Drawing" section: 

  • Window animation scale
  • Transition animation scale
  • Animator duration scale

5. Write Your First Test in Espresso

First, we start off testing a Login screen. Here's how the login flow starts: the user launches the app, and the first screen shown contains a single Login button. When that Login button is clicked, it opens up the LoginActivity screen. This screen contains just two EditTexts (the username and password fields) and a Submit button. 

Here's what our MainActivity layout looks like:

MainActivity layout

Here's what our LoginActivity layout looks like:

Let's now write a test for our MainActivity class. Go to your MainActivity class, move the cursor to the MainActivity name, and press Shift-Control-T. Select Create New Test... in the popup menu. 

Create new test dialog

Press the OK button, and another dialog shows up. Choose the androidTest directory and click the OK button once more. Note that because we are writing an instrumentation test (Android SDK specific tests), the test cases reside in the androidTest/java folder. 

Now, Android Studio has successfully created a test class for us. Above the class name, include this annotation: @RunWith(AndroidJUnit4::class).

This annotation signifies that all the tests in this class are Android-specific tests.

Testing Activities

Because we want to test an Activity, we have to do a little setup. We need to inform Espresso which Activity to open or launch before executing and destroy after executing any test method. 

Note that the @Rule annotation means that this is a JUnit4 test rule. JUnit4 test rules are run before and after every test method (annotated with @Test). In our own scenario, we want to launch MainActivity before every test method and destroy it after. 

We also included the @JvmField Kotlin annotation. This simply instructs the compiler not to generate getters and setters for the property and instead to expose it as a simple Java field.

Here are the three major steps in writing an Espresso test:

  • Look for the widget (e.g. TextView or Button) you want to test.
  • Perform one or more actions on that widget. 
  • Verify or check to see if that widget is now in a certain state. 

The following types of annotations can be applied to the methods used inside the test class.

  • @BeforeClass: this indicates that the static method this annotation is applied to must be executed once and before all tests in the class. This could be used, for example, to set up a connection to a database. 
  • @Before: indicates that the method this annotation is attached to must be executed before each test method in the class.
  • @Test: indicates that the method this annotation is attached to should run as a test case.
  • @After: indicates that the method this annotation is attached to should run after each test method. 
  • @AfterClass: indicates that the method this annotation is attached to should run after all the test methods in the class have been run. Here, we typically close out resources that were opened in @BeforeClass

Find a View Using onView()

In our MainActivity layout file, we just have one widget—the Login button. Let's test a scenario where a user will find that button and click on it.

To find widgets in Espresso, we make use of the onView() static method (instead of findViewById()). The parameter type we supply to onView() is a Matcher. Note that the Matcher API doesn't come from the Android SDK but instead from the Hamcrest Project. Hamcrest's matcher library is inside the Espresso library we pulled via Gradle. 

The onView(withId(R.id.btn_login)) will return a ViewInteraction that is for a View whose ID is R.id.btn_login. In the example above, we used withId() to look for a widget with a given id. Other view matchers we can use are: 

  • withText(): returns a matcher that matches TextView based on its text property value.
  • withHint(): returns a matcher that matches TextView based on its hint property value.
  • withTagKey(): returns a matcher that matches View based on tag keys.
  • withTagValue(): returns a matcher that matches Views based on tag property values.

First, let's test to see if the button is actually displayed on the screen. 

Here, we are just confirming if the button with the given id (R.id.btn_login) is visible to the user, so we use the check() method to confirm if the underlying View has a certain state—in our case, if it is visible.

The matches() static method returns a generic ViewAssertion that asserts that a view exists in the view hierarchy and is matched by the given view matcher. That given view matcher is returned by calling isDisplayed(). As suggested by the method name, isDisplayed() is a matcher that matches Views that are currently displayed on the screen to the user. For example, if we want to check if a button is enabled, we simply pass isEnabled() to matches()

Other popular view matchers we can pass into the matches() method are:

  • hasFocus(): returns a matcher that matches Views that currently have focus.
  • isChecked(): returns a matcher that accepts if and only if the view is a CompoundButton (or subtype of) and is in checked state. The opposite of this method is isNotChecked()
  • isSelected(): returns a matcher that matches Views that are selected.

To run the test, you can click the green triangle beside the method or the class name. Clicking the green triangle beside the class name will run all the test methods in that class, while the one beside a method will run the test only for that method. 

MainActivityTest class

Hooray! Our test passed!

Android Studio test result toolbar

Perform Actions on a View

On a ViewInteraction object which is returned by calling onView(), we can simulate actions a user can perform on a widget. For example, we can simulate a click action by simply calling the click() static method inside the ViewActions class. This will return a ViewAction object for us. 

The documentation says that ViewAction is:

Responsible for performing an interaction on the given View element.

We perform a click event by first calling perform(). This method performs the given action(s) on the view selected by the current view matcher. Note that we can pass it a single action or a list of actions (executed in order). Here, we gave it click(). Other possible actions are:

  • typeText() to imitate typing text into an EditText.
  • clearText() to simulate clearing text in an EditText.
  • doubleClick() to simulate double-clicking a View.
  • longClick() to imitate long-clicking a View.
  • scrollTo() to simulate scrolling a ScrollView to a particular View that is visible. 
  • swipeLeft() to simulate swiping right to left across the vertical center of a View.

Many more simulations can be found inside the ViewActions class

Validate With View Assertions

Let's complete our test, to validate that the LoginActivity screen is shown whenever the Login button is clicked. Though we have already seen how to use check() on a ViewInteraction, let's use it again, passing it another ViewAssertion

Inside the LoginActivity layout file, apart from EditTexts and a Button, we also have a TextView with ID R.id.tv_login. So we simply do a check to confirm that the TextView is visible to the user. 

Now you can run the test again!

Android Studio test result toolbar

Your tests should pass successfully if you followed all the steps correctly. 

Here's what happened during the process of executing our tests: 

  1. Launched the MainActivity using the activityRule field.
  2. Verified if the Login button (R.id.btn_login) was visible (isDisplayed()) to the user.
  3. Simulated a click action (click()) on that button.
  4. Verified if the LoginActivity was shown to the user by checking if a TextView with id R.id.tv_login in the LoginActivity is visible.

You can always refer to the Espresso cheat sheet to see the different view matchers, view actions, and view assertions available. 

6. Test the LoginActivity Screen

Here's our LoginActivity.kt:

In the code above, if the entered username is "chike" and the password is "password", then the login is successful. For any other input, it's a failure. Let's now write an Espresso test for this!

Go to LoginActivity.kt, move the cursor to the LoginActivity name, and press Shift-Control-T. Select Create New Test... in the popup menu. Follow the same process as we did for MainActivity.kt, and click the OK button. 

This test class is very similar to our first one. If we run the test, our LoginActivity screen is opened. The username and password are typed into the R.id.et_username and R.id.et_password fields respectively. Next, Espresso will click the Submit button (R.id.btn_submit). It will wait until a View with id R.id.tv_login can be found with text reading Success

7. Test a RecyclerView

RecyclerViewActions is the class that exposes a set of APIs to operate on a RecyclerView. RecyclerViewActions is part of a separate artifact inside the espresso-contrib artifact, which also should be added to build.gradle:

Note that this artifact also contains the API for UI testing the navigation drawer through DrawerActions and DrawerMatchers

To click on an item at any position in a RecyclerView, we invoke actionOnItemAtPosition(). We have to give it a type of item. In our case, the item is the ViewHolder class inside our RandomAdapter. This method also takes in two parameters; the first is the position, and the second is the action (ViewActions.click()). 

Other RecyclerViewActions that can be performed are:

  • actionOnHolderItem(): performs a ViewAction on a view matched by viewHolderMatcher. This allows us to match it by what's contained inside the ViewHolder rather than the position. 
  • scrollToPosition(): returns a ViewAction which scrolls RecyclerView to a position.

Next (once the "add note screen" is open), we will enter our note text and save the note. We don't need to wait for the new screen to open—Espresso will do this automatically for us. It waits until a View with the id R.id.add_note_title can be found.

8. Test Intents

Espresso makes use of another artifact named espresso-intents for testing intents. This artifact is just another extension to Espresso that focuses on the validation and mocking of Intents. Let's look at an example.

First, we have to pull the espresso-intents library into our project. 

IntentsTestRule extends ActivityTestRule, so they both have similar behaviours. Here's what the doc says:

This class is an extension of ActivityTestRule, which initializes Espresso-Intents before each test annotated with Test and releases Espresso-Intents after each test run. The Activity will be terminated after each test and this rule can be used in the same way as ActivityTestRule.

The main differentiating feature is that it has additional functionalities for testing startActivity() and startActivityForResult() with mocks and stubs. 

We are now going to test a scenario where a user will click on a button (R.id.btn_select_contact) on the screen to pick a contact from the phone's contact list. 

Here we are using intending() from the espresso-intents library to set up a stub with a mock response for our ACTION_PICK request. Here's what happens in  PickContactActivity.kt when the user clicks the button with id R.id.btn_select_contact to pick a contact.

intending() takes in a Matcher that matches intents for which stubbed response should be provided. In other words, the Matcher identifies which request you're interested in stubbing. In our own case, we make use of hasAction() (a helper method in IntentMatchers) to find our ACTION_PICK request. We then invoke respondWith(), which sets the result for onActivityResult(). In our case, the result has Activity.RESULT_OK, simulating the user selecting a contact from the list. 

We then simulate clicking the select contact button, which calls startActivityForResult(). Note that our stub sent the mock response to onActivityResult()

Finally, we use the intended() helper method to simply validate that the calls to startActivity() and startActivityForResult() were made with the right information. 

Conclusion

In this tutorial, you learned how to easily use the Espresso testing framework in your Android Studio project to automate your test workflow. 

I highly recommend checking out the official documentation to learn more about writing UI tests with Espresso. 

2018-09-17T12:53:38.000Z2018-09-17T12:53:38.000ZChike Mgbemena

Viewing all articles
Browse latest Browse all 1836

Trending Articles