25 min read

In this article by the author, Paul Blundell, of the book, Learning Android Application Testing, we learn to start digging a bit deeper to recognize the building blocks available to create more useful tests.

We will be covering the following topics:

  • Common assertions
  • View assertions
  • Other assertion types
  • Helpers to test User Interfaces
  • Mock objects
  • Instrumentation
  • TestCase class hierarchies
  • Using external libraries

We will be analyzing these components and showing examples of their use when applicable. The examples in this article are intentionally split from the original Android project that contains them. This is done to let you concentrate and focus only on the subject being presented, though the complete examples in a single project can be downloaded as explained later. Right now, we are interested in the trees and not the forest.

Along with the examples presented, we will be identifying reusable common patterns that will help you in the creation of tests for your own projects.

(For more resources related to this topic, see here.)

The demonstration application

A very simple application has been created to demonstrate the use of some of the tests in this article. The source for the application can be downloaded from XXXXXXXXXXXXX.

The following screenshot shows this application running:

Learning Android Application Testing

When reading the explanation of the tests in this article, at any point, you can refer to the demo application that is provided in order to see the test in action. The previous simple application has a clickable link, text input, click on a button and a defined layout UI, we can test these one by one.

Assertions in depth

Assertions are methods that check for a condition that can be evaluated. If the condition is not met, the assertion method will throw an exception, thereby aborting the execution of the test.

The JUnit API includes the class Assert. This is the base class of all the TestCase classes that hold several assertion methods useful for writing tests. These inherited methods test for a variety of conditions and are overloaded to support different parameter types. They can be grouped together in the following different sets, depending on the condition checked, for example:

  • assertEquals
  • assertTrue
  • assertFalse
  • assertNull
  • assertNotNull
  • assertSame
  • assertNotSame
  • fail

The condition tested is pretty obvious and is easily identifiable by the method name. Perhaps the ones that deserve some attention are assertEquals() and assertSame(). The former, when used on objects, asserts that both objects passed as parameters are equally calling the objects’ equals() method. The latter asserts that both objects refer to the same object. If, in some case, equals() is not implemented by the class, then assertEquals() and assertSame() will do the same thing.

When one of these assertions fails inside a test, an AssertionFailedException is thrown, and this indicates that the test has failed.

Occasionally, during the development process, you might need to create a test that you are not implementing at that precise time. However, you want to flag that the creation of the test was postponed. In such cases, you can use the fail() method, which always fails and uses a custom message that indicates the condition:

public void testNotImplementedYet() {
   fail("Not implemented yet");
}

Still, there is another common use for fail() that is worth mentioning. If we need to test whether a method throws an exception, we can surround the code with a try-catch block and force a fail if the exception was not thrown. For example:

public void testShouldThrowException() {
   try {
   MyFirstProjectActivity.methodThatShouldThrowException();
     fail("Exception was not thrown");
   } catch ( Exception ex ) {
     // do nothing
   }
}

JUnit4 has the annotation @Test(expected=Exception.class), and this supersedes the need for using fail() when testing exceptions. With this annotation, the test will only pass if the expected exception is thrown.

Custom message

It is worth knowing that all assert methods provide an overloaded version including a custom String message. Should the assertion fail, this custom message will be printed by the test runner, instead of a default message.

The premise behind this is that, sometimes, the generic error message does not reveal enough details, and it is not obvious how the test failed. This custom message can be extremely useful to easily identify the failure once you are looking at the test report, so it’s highly recommended as a best practice to use this version.

The following is an example of a simple test that uses this recommendation:

public void testMax() {
int a = 10;
int b = 20;
 
int actual = Math.max(a, b);
 
String failMsg = "Expected: " + b + " but was: " + actual;
assertEquals(failMsg, b, actual);
}

In the preceding example, we can see another practice that would help you organize and understand your tests easily. This is the use of explicit names for variables that hold the actual values.

There are other libraries available that have better default error messages and also a more fluid interface for testing. One of these that is worth looking at is Fest (https://code.google.com/p/fest/).

Static imports

Though basic assertion methods are inherited from the Assert base class, some other assertions need specific imports. To improve the readability of your tests, there is a pattern to statically import the assert methods from the corresponding classes. Using this pattern instead of having:

public void testAlignment() {
int margin = 0;
   ...
android.test.ViewAsserts.assertRightAligned
(errorMsg, editText, margin);
}

We can simplify it by adding the static import:

import static android.test.ViewAsserts.assertRightAligned;
public void testAlignment() {
   int margin = 0;
   assertRightAligned(errorMsg, editText, margin);
}

View assertions

The assertions introduced earlier handle a variety of types as parameters, but they are only intended to test simple conditions or simple objects.

For example, we have asertEquals(short expected, short actual) to test short values, assertEquals(int expected, int actual) to test integer values, assertEquals(Object expected, Object expected) to test any Object instance, and so on.

Usually, while testing user interfaces in Android, you will face the problem of more sophisticated methods, which are mainly related with Views. In this respect, Android provides a class with plenty of assertions in android.test.ViewAsserts (see http://developer.android.com/reference/android/test/ViewAsserts.html for more details), which test relationships between Views and their absolute and relative positions on the screen.

These methods are also overloaded to provide different conditions. Among the assertions, we can find the following:

  • assertBaselineAligned: This asserts that two Views are aligned on their baseline; that is, their baselines are on the same y location.
  • assertBottomAligned: This asserts that two views are bottom aligned; that is, their bottom edges are on the same y location.
  • assertGroupContains: This asserts that the specified group contains a specific child once and only once.
  • assertGroupIntegrity: This asserts the specified group’s integrity. The child count should be >= 0 and each child should be non-null.
  • assertGroupNotContains: This asserts that the specified group does not contain a specific child.
  • assertHasScreenCoordinates: This asserts that a View has a particular x and y position on the visible screen.
  • assertHorizontalCenterAligned: This asserts that the test View is horizontally center aligned with respect to the reference view.
  • assertLeftAligned: This asserts that two Views are left aligned; that is, their left edges are on the same x location. An optional margin can also be provided.
  • assertOffScreenAbove: This asserts that the specified view is above the visible screen.
  • assertOffScreenBelow: This asserts that the specified view is below the visible screen.
  • assertOnScreen: This asserts that a View is on the screen.
  • assertRightAligned: This asserts that two Views are right-aligned; that is, their right edges are on the same x location. An optional margin can also be specified.
  • assertTopAligned: This asserts that two Views are top aligned; that is, their top edges are on the same y location. An optional margin can also be specified.
  • assertVerticalCenterAligned: This asserts that the test View is vertically center-aligned with respect to the reference View.

The following example shows how you can use ViewAssertions to test the user interface layout:

public void testUserInterfaceLayout() {
   int margin = 0;
   View origin = mActivity.getWindow().getDecorView();
   assertOnScreen(origin, editText);
   assertOnScreen(origin, button);
   assertRightAligned(editText, button, margin);
}

The assertOnScreen method uses an origin to start looking for the requested Views. In this case, we are using the top-level window decor View. If, for some reason, you don’t need to go that high in the hierarchy, or if this approach is not suitable for your test, you may use another root View in the hierarchy, for example View.getRootView(), which, in our concrete example, would be editText.getRootView().

Even more assertions

If the assertions that are reviewed previously do not seem to be enough for your tests’ needs, there is still another class included in the Android framework that covers other cases. This class is MoreAsserts (http://developer.android.com/reference/android/test/MoreAsserts.html).

These methods are also overloaded to support different parameter types. Among the assertions, we can find the following:

  • assertAssignableFrom: This asserts that an object is assignable to a class.
  • assertContainsRegex: This asserts that an expected Regex matches any substring of the specified String. It fails with the specified message if it does not.
  • assertContainsInAnyOrder: This asserts that the specified Iterable contains precisely the elements expected, but in any order.
  • assertContainsInOrder: This asserts that the specified Iterable contains precisely the elements expected, but in the same order.
  • assertEmpty: This asserts that an Iterable is empty.
  • assertEquals: This is for some Collections not covered in JUnit asserts.
  • assertMatchesRegex: This asserts that the specified Regex exactly matches the String and fails with the provided message if it does not.
  • assertNotContainsRegex: This asserts that the specified Regex does not match any substring of the specified String, and fails with the provided message if it does.
  • assertNotEmpty: This asserts that some Collections not covered in JUnit asserts are not empty.
  • assertNotMatchesRegex: This asserts that the specified Regex does not exactly match the specified String, and fails with the provided message if it does.
  • checkEqualsAndHashCodeMethods: This is a utility used to test the equals() and hashCode() results at once. This tests whether equals() that is applied to both objects matches the specified result.

The following test checks for an error during the invocation of the capitalization method called via a click on the UI button:

@UiThreadTest
public void testNoErrorInCapitalization() {
String msg = "capitalize this text";
editText.setText(msg);
 
button.performClick();
 
String actual = editText.getText().toString();
String notExpectedRegexp = "(?i:ERROR)";
String errorMsg = "Capitalization error for " + actual;
assertNotContainsRegex(errorMsg, notExpectedRegexp, actual);
}

If you are not familiar with regular expressions, invest some time and visit http://developer.android.com/reference/java/util/regex/package-summary.html because it will be worth it!

In this particular case, we are looking for the word ERROR contained in the result with a case-insensitive match (setting the flag i for this purpose). That is, if for some reason, capitalization doesn’t work in our application, and it contains an error message, we can detect this condition with the assertion.

Note that because this is a test that modifies the user interface, we must annotate it with @UiThreadTest; otherwise, it won’t be able to alter the UI from a different thread, and we will receive the following exception:

INFO/TestRunner(610): ----- begin exception -----
INFO/TestRunner(610): android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.
INFO/TestRunner(610):     at android.view.ViewRoot.checkThread(ViewRoot.java:2932)
[...]
INFO/TestRunner(610):     at android.app.
Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
INFO/TestRunner(610): ----- end exception -----

The TouchUtils class

Sometimes, when testing UIs, it is helpful to simulate different kinds of touch events. These touch events can be generated in many different ways, but probably android.test.TouchUtils is the simplest to use. This class provides reusable methods to generate touch events in test cases that are derived from InstrumentationTestCase.

The featured methods allow a simulated interaction with the UI under test. The TouchUtils class provides the infrastructure to inject the events using the correct UI or main thread, so no special handling is needed, and you don’t need to annotate the test using @UIThreadTest.

TouchUtils supports the following:

  • Clicking on a View and releasing it
  • Tapping on a View (touching it and quickly releasing)
  • Long-clicking on a View
  • Dragging the screen
  • Dragging Views

The following test represents a typical usage of TouchUtils:

   public void testListScrolling() {
       listView.scrollTo(0, 0);
 
       TouchUtils.dragQuarterScreenUp(this, activity);
       int actualItemPosition = listView.getFirstVisiblePosition();
 
       assertTrue("Wrong position", actualItemPosition > 0);
   }

This test does the following:

  • Repositions the list at the beginning to start from a known condition
  • Scrolls the list
  • Checks for the first visible position to see that it was correctly scrolled

Even the most complex UIs can be tested in that way, and it would help you detect a variety of conditions that could potentially affect the user experience.

Mock objects

We have seen the mock objects provided by the Android testing framework, and evaluated the concerns about not using real objects to isolate our tests from the surrounding environment.

Martin Fowler calls these two styles the classical and mockist Test-driven Development dichotomy in his great article Mocks aren’t stubs, which can be read online at http://www.martinfowler.com/articles/mocksArentStubs.html.

Independent of this discussion, we are introducing mock objects as one of the available building blocks because, sometimes, using mock objects in our tests is recommended, desirable, useful, or even unavoidable.

The Android SDK provides the following classes in the subpackage android.test.mock to help us:

  • MockApplication: This is a mock implementation of the Application class. All methods are non-functional and throw UnsupportedOperationException.
  • MockContentProvider: This is a mock implementation of ContentProvider. All methods are non-functional and throw UnsupportedOperationException.
  • MockContentResolver: This is a mock implementation of the ContentResolver class that isolates the test code from the real content system. All methods are non-functional and throw UnsupportedOperationException.
  • MockContext: This is a mock context class, and this can be used to inject other dependencies. All methods are non-functional and throw UnsupportedOperationException.
  • MockCursor: This is a mock Cursor class that isolates the test code from real Cursor implementation. All methods are non-functional and throw UnsupportedOperationException.
  • MockDialogInterface: This is a mock implementation of the DialogInterface class. All methods are non-functional and throw UnsupportedOperationException.
  • MockPackageManager: This is a mock implementation of the PackageManager class. All methods are non-functional and throw UnsupportedOperationException.
  • MockResources: This is a mock Resources class.

All of these classes have non-functional methods that throw UnsupportedOperationException when used. If you need to use some of these methods, or if you detect that your test is failing with this Exception, you should extend one of these base classes and provide the required functionality.

MockContext overview

This mock can be used to inject other dependencies, mocks, or monitors into the classes under test. Extend this class to provide your desired behavior, overriding the correspondent methods. The Android SDK provides some prebuilt mock Context objects, each of which has a separate use case.

The IsolatedContext class

In your tests, you might find the need to isolate the Activity under test from other Android components to prevent unwanted interactions. This can be a complete isolation, but sometimes, this isolation avoids interacting with other components, and for your Activity to still run correctly, some connection with the system is required.

For those cases, the Android SDK provides android.test.IsolatedContext, a mock Context that not only prevents interaction with most of the underlying system but also satisfies the needs of interacting with other packages or components such as Services or ContentProviders.

Alternate route to file and database operations

In some cases, all we need is to be able to provide an alternate route to the file and database operations. For example, if we are testing the application on a real device, we perhaps don’t want to affect the existing database but use our own testing data.

Such cases can take advantage of another class that is not part of the android.test.mock subpackage but is part of android.test instead, that is, RenamingDelegatingContext.

This class lets us alter operations on files and databases by having a prefix that is specified in the constructor. All other operations are delegated to the delegating Context that you must specify in the constructor too.

Suppose our Activity under test uses a database we want to control, probably introducing specialized content or fixture data to drive our tests, and we don’t want to use the real files. In this case, we create a RenamingDelegatingContext class that specifies a prefix, and our unchanged Activity will use this prefix to create any files.

For example, if our Activity tries to access a file named birthdays.txt, and we provide a RenamingDelegatingContext class that specifies the prefix test, then this same Activity will access the file testbirthdays.txt instead when it is being tested.

The MockContentResolver class

The MockContentResolver class implements all methods in a non-functional way and throws the exception UnsupportedOperationException if you attempt to use them. The reason for this class is to isolate tests from the real content.

Let’s say your application uses a ContentProvider class to feed your Activity information. You can create unit tests for this ContentProvider using ProviderTestCase2, which we will be analyzing shortly, but when we try to produce functional or integration tests for the Activity against ContentProvider, it’s not so evident as to what test case to use. The most obvious choice is ActivityInstrumentationTestCase2, mainly if your functional tests simulate user experience because you might need the sendKeys() method or similar methods, which are readily available on these tests.

The first problem you might encounter then is that it’s unclear as to where to inject a MockContentResolver in your test to be able to use test data with your ContentProvider. There’s no way to inject a MockContext either.

The TestCase base class

This is the base class of all other test cases in the JUnit framework. It implements the basic methods that we were analyzing in the previous examples (setUp()). The TestCase class also implements the junit.framework.Test interface, meaning it can be run as a JUnit test.

Your Android test cases should always extend TestCase or one of its descendants.

The default constructor

All test cases require a default constructor because, sometimes, depending on the test runner used, this is the only constructor that is invoked, and is also used for serialization.

According to the documentation, this method is not intended to be used by “mere mortals” without calling setName(String name).

Therefore, to appease the Gods, a common pattern is to use a default test case name in this constructor and invoke the given name constructor afterwards:

public class MyTestCase extends TestCase {
   public MyTestCase() {
     this("MyTestCase Default Name");
   }
 
   public MyTestCase(String name) {
     super(name);
   }
}

The given name constructor

This constructor takes a name as an argument to label the test case. It will appear in test reports and would be of much help when you try to identify where failed tests have come from.

The setName() method

There are some classes that extend TestCase that don’t provide a given name constructor. In such cases, the only alternative is to call setName(String name).

The AndroidTestCase base class

This class can be used as a base class for general-purpose Android test cases.

Use it when you need access to Android resources, databases, or files in the filesystem. Context is stored as a field in this class, which is conveniently named mContext and can be used inside the tests if needed, or the getContext() method can be used too.

Tests based on this class can start more than one Activity using Context.startActivity().

There are various test cases in Android SDK that extend this base class:

  • ApplicationTestCase<T extends Application>
  • ProviderTestCase2<T extends ContentProvider>
  • ServiceTestCase<T extends Service>

When using the AndroidTestCase Java class, you inherit some base assertion methods that can be used; let’s look at these in more detail.

The assertActivityRequiresPermission() method

The signature for this method is as follows:

public void assertActivityRequiresPermission
(String packageName, String className, String permission)

Description

This assertion method checks whether the launching of a particular Activity is protected by a specific permission. It takes the following three parameters:

  • packageName: This is a string that indicates the package name of the activity to launch
  • className: This is a string that indicates the class of the activity to launch
  • permission: This is a string with the permission to check

The Activity is launched and then SecurityException is expected, which mentions that the required permission is missing in the error message. The actual instantiation of an activity is not handled by this assertion, and thus, an Instrumentation is not needed.

Example

This test checks the requirement of the android.Manifest.permission.WRITE_EXTERNAL_STORAGE permission, which is needed to write to external storage, in the MyContactsActivity Activity:

public void testActivityPermission() {
String pkg = "com.blundell.tut";
String activity = PKG + ".MyContactsActivity";
String permission = android.Manifest.permission.CALL_PHONE;
assertActivityRequiresPermission(pkg, activity, permission);
}

Always use the constants that describe the permissions from android.Manifest.permission, not the strings, so if the implementation changes, your code will still be valid.

The assertReadingContentUriRequiresPermission method

The signature for this method is as follows:

public void assertReadingContentUriRequiresPermission(Uri uri, String permission)

Description

This assertion method checks whether reading from a specific URI requires the permission provided as a parameter.

It takes the following two parameters:

  • uri: This is the Uri that requires a permission to query
  • permission: This is a string that contains the permission to query

If a SecurityException class is generated, which contains the specified permission, this assertion is validated.

Example

This test tries to read contacts and verifies that the correct SecurityException is generated:

public void testReadingContacts() {
   Uri URI = ContactsContract.AUTHORITY_URI;
   String PERMISSION = android.Manifest.permission.READ_CONTACTS;
   assertReadingContentUriRequiresPermission(URI, PERMISSION);
}

The assertWritingContentUriRequiresPermission() method

The signature for this method is as follows:

public void assertWritingContentUriRequiresPermission (Uri uri,   String permission)

Description

This assertion method checks whether inserting into a specific Uri requires the permission provided as a parameter.

It takes the following two parameters:

  • uri: This is the Uri that requires a permission to query
  • permission: This is a string that contains the permission to query

If a SecurityException class is generated, which contains the specified permission, this assertion is validated.

Example

This test tries to write to Contacts and verifies that the correct SecurityException is generated:

public void testWritingContacts() {
Uri uri = ContactsContract.AUTHORITY_URI;
   String permission = android.Manifest.permission.WRITE_CONTACTS;
assertWritingContentUriRequiresPermission(uri, permission);
}

Instrumentation

Instrumentation is instantiated by the system before any of the application code is run, thereby allowing monitoring of all the interactions between the system and the application.

As with many other Android application components, instrumentation implementations are described in the AndroidManifest.xml under the tag <instrumentation>. However, with the advent of Gradle, this has now been automated for us, and we can change the properties of the instrumentation in the app’s build.gradle file. The AndroidManifest file for your tests will be automatically generated:

defaultConfig {
testApplicationId 'com.blundell.tut.tests'
testInstrumentationRunner   "android.test.InstrumentationTestRunner"
}

The values mentioned in the preceding code are also the defaults if you do not declare them, meaning that you don’t have to have any of these parameters to start writing tests.

The testApplicationId attribute defines the name of the package for your tests. As a default, it is your application under the test package name + tests. You can declare a custom test runner using testInstrumentationRunner. This is handy if you want to have tests run in a custom way, for example, parallel test execution.

There are also many other parameters in development, and I would advise you to keep your eyes upon the Google Gradle plugin website (http://tools.android.com/tech-docs/new-build-system/user-guide).

The ActivityMonitor inner class

As mentioned earlier, the Instrumentation class is used to monitor the interaction between the system and the application or the Activities under test. The inner class Instrumentation ActivityMonitor allows the monitoring of a single Activity within an application.

Example

Let’s pretend that we have a TextView in our Activity that holds a URL and has its auto link property set:

<TextView
       android_id="@+id/link
       android_layout_width="match_parent"
   android_layout_height="wrap_content"
       android_text="@string/home"
   android_autoLink="web" " />

If we want to verify that, when clicked, the hyperlink is correctly followed and some browser is invoked, we can create a test like this:

public void testFollowLink() {
       IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
       intentFilter.addDataScheme("http");
       intentFilter.addCategory(Intent.CATEGORY_BROWSABLE);
 
       Instrumentation inst = getInstrumentation();
       ActivityMonitor monitor = inst.addMonitor(intentFilter, null, false);
       TouchUtils.clickView(this, linkTextView);
       monitor.waitForActivityWithTimeout(3000);
       int monitorHits = monitor.getHits();
       inst.removeMonitor(monitor);
 
       assertEquals(1, monitorHits);
   }

Here, we will do the following:

  1. Create an IntentFilter for intents that would open a browser.
  2. Add a monitor to our Instrumentation based on the IntentFilter class.
  3. Click on the hyperlink.
  4. Wait for the activity (hopefully the browser).
  5. Verify that the monitor hits were incremented.
  6. Remove the monitor.

Using monitors, we can test even the most complex interactions with the system and other Activities. This is a very powerful tool to create integration tests.

The InstrumentationTestCase class

The InstrumentationTestCase class is the direct or indirect base class for various test cases that have access to Instrumentation. This is the list of the most important direct and indirect subclasses:

  • ActivityTestCase
  • ProviderTestCase2<T extends ContentProvider>
  • SingleLaunchActivityTestCase<T extends Activity>
  • SyncBaseInstrumentation
  • ActivityInstrumentationTestCase2<T extends Activity>
  • ActivityUnitTestCase<T extends Activity>

The InstrumentationTestCase class is in the android.test package, and extends junit.framework.TestCase, which extends junit.framework.Assert.

The launchActivity and launchActivityWithIntent method

These utility methods are used to launch Activities from a test. If the Intent is not specified using the second option, a default Intent is used:

public final T launchActivity (String pkg, Class<T> activityCls,   Bundle extras)

The template class parameter T is used in activityCls and as the return type, limiting its use to Activities of that type.

If you need to specify a custom Intent, you can use the following code that also adds the intent parameter:

public final T launchActivityWithIntent (String pkg, Class<T>   activityCls, Intent intent)

The sendKeys and sendRepeatedKeys methods

While testing Activities’ UI, you will face the need to simulate interaction with qwerty-based keyboards or DPAD buttons to send keys to complete fields, select shortcuts, or navigate throughout the different components.

This is what the different sendKeys and sendRepeatedKeys are used for.

There is one version of sendKeys that accepts integer keys values. They can be obtained from constants defined in the KeyEvent class.

For example, we can use the sendKeys method in this way:

   public void testSendKeyInts() {
       requestMessageInputFocus();
       sendKeys(
               KeyEvent.KEYCODE_H,
               KeyEvent.KEYCODE_E,
               KeyEvent.KEYCODE_E,
               KeyEvent.KEYCODE_E,
               KeyEvent.KEYCODE_Y,
               KeyEvent.KEYCODE_DPAD_DOWN,
               KeyEvent.KEYCODE_ENTER);
       String actual = messageInput.getText().toString();
 
       assertEquals("HEEEY", actual);
   }

Here, we are sending H, E, and Y letter keys and then the ENTER key using their integer representations to the Activity under test.

Alternatively, we can create a string by concatenating the keys we desire to send, discarding the KEYCODE prefix, and separating them with spaces that are ultimately ignored:

     public void testSendKeyString() {
       requestMessageInputFocus();
 
       sendKeys("H 3*E Y DPAD_DOWN ENTER");
       String actual = messageInput.getText().toString();
 
       assertEquals("HEEEY", actual);
   }

Here, we did exactly the same as in the previous test but we used a String “H 3* EY DPAD_DOWN ENTER”. Note that every key in the String can be prefixed by a repeating factor followed by * and the key to be repeated. We used 3*E in our previous example, which is the same as E E E, that is, three times the letter E.

If sending repeated keys is what we need in our tests, there is also another alternative that is precisely intended for these cases:

public void testSendRepeatedKeys() {
       requestMessageInputFocus();
 
       sendRepeatedKeys(
               1, KeyEvent.KEYCODE_H,
               3, KeyEvent.KEYCODE_E,
               1, KeyEvent.KEYCODE_Y,
               1, KeyEvent.KEYCODE_DPAD_DOWN,
               1, KeyEvent.KEYCODE_ENTER);
       String actual = messageInput.getText().toString();
 
       assertEquals("HEEEY", actual);
   }

This is the same test implemented in a different manner. The repetition number precedes each key.

The runTestOnUiThread helper method

The runTestOnUiThread method is a helper method used to run portions of a test on the UI thread. We used this inside the method requestMessageInputFocus(); so that we can set the focus on our EditText before waiting for the application to be idle, using Instrumentation.waitForIdleSync(). Also, the runTestOnUiThread method throws an exception, so we have to deal with this case:

private void requestMessageInputFocus() {
       try {
           runTestOnUiThread(new Runnable() {
               @Override
               public void run() {
                   messageInput.requestFocus();
               }
           });
       } catch (Throwable throwable) {
           fail("Could not request focus.");
       }
       instrumentation.waitForIdleSync();
   }

Alternatively, as we have discussed before, to run a test on the UI thread, we can annotate it with @UiThreadTest. However, sometimes, we need to run only parts of the test on the UI thread because other parts of it are not suitable to run on that thread, for example, database calls, or we are using other helper methods that provide the infrastructure themselves to use the UI thread, for example the TouchUtils methods.

Summary

We investigated the most relevant building blocks and reusable patterns to create our tests. Along this journey, we:

  • Understood the common assertions found in JUnit tests
  • Explained the specialized assertions found in the Android SDK
  • Explored Android mock objects and their use in Android tests

Now that we have all the building blocks, it is time to start creating more and more tests to acquire the experience needed to master the technique.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here