Touring the Tests

When we write Android apps, a chunk of our time is spent testing those apps. Some of that testing is manual: poking at the UI and seeing if everything works as expected. But some of that testing is automated, with test classes that test our “real” classes and confirm that everything is OK.

With that in mind, let’s take a look at the types of tests that we have in the starter project and how to run them.

Instrumented Tests

There are two major types of test in an Android app:

Unit tests run much faster, but they cannot test as much, because they do not have access to everything inside of Android. For example, while we could test our ability to talk to a Web service from unit tests, we cannot test our ability to get GPS locations using Android APIs from unit tests. For those, we need instrumented tests. Similarly, most automated UI testing needs instrumented tests, as the Android UI system is only really available in Android.

Since they are more flexible, and since test speed only becomes a major issue with larger projects, let’s focus first on instrumented tests.

Where They Run

As noted above, instrumented tests will run on an Android device or emulator. For your own personal test runs, you can use the same devices or emulators that you use for manually running the app.

Projects that employ continuous integration (CI) servers will need to configure them to support running tests on server-hosted emulators. Some hosted CI services — such as CircleCI — have that capability readily available to you. For self-hosted CI servers, there should be recipes available to teach you how to configure them for Android app testing.

What You Can Test

Because you are running the tests in an actual Android environment, you can test anything that you want. You have the full Android SDK at your disposal.

However, from a practical standpoint, there will be limits as to what you can test:

What the Starter Project Has

The starter project not only has a “hello, world” sort of UI for you, but it has a similar instrumented test set up, ready for you to run.

The androidTest Source Set

As we saw earlier in the book, instrumented test code resides in an androidTest/ directory. This is a peer to the main/ directory that contains your “real” application code. androidTest/ is a “source set” that will be used only when running instrumented tests. The stuff in the androidTest/ source set will not be included in your app when you ship it.

The Test Class

Inside of there you will find a java/ directory, with a Java package matching the application ID of your app, and an ExampleInstrumentedTest Java or Kotlin file.

When you create a new project, and you choose whether or not to have Kotlin support, that choice will determine not only whether your MainActivity is in Java or Kotlin, but also whether your test code is in Java or Kotlin.

The Kotlin class is fairly short:

package com.commonsware.jetpack.hello

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
  @Test
  fun useAppContext() {
    // Context of the app under test.
    val appContext = InstrumentationRegistry.getInstrumentation().targetContext
    assertEquals("com.commonsware.jetpack.hello", appContext.packageName)
  }
}

The Java equivalent is not much longer:

package com.commonsware.jetpack.hello;

import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
  @Test
  public void useAppContext() {
    // Context of the app under test.
    Context appContext =
      InstrumentationRegistry.getInstrumentation().getTargetContext();
    assertEquals("com.commonsware.jetpack.hello", appContext.getPackageName());
  }
}

The Annotations

The Java and Kotlin editions of the test class are equivalent, other than language syntax.

Both have a single class, named ExampleInstrumentedTest, annotated with a @RunWith(AndroidJUnit4::class) annotation. Android presently uses JUnit 4 for instrumented tests. This annotation tells JUnit — and, more importantly, some Android Studio stuff for running tests — that this class contains test code that should be run as part of an instrumented test.

Both editions of ExampleInstrumentedTest have one method (or function, in Kotlin). It is called useAppContext(), and it is marked with the @Test annotation. A test class can contain one or more of these @Test methods/functions. When it comes time to run the tests, Android Studio will:

The Test Code

So… what is useAppContext() testing?

In truth, it is not testing very much.

We will explore what a Context is a bit later in the book. For the moment, take it on faith that this code is:

assertEquals() is supplied by JUnit 4 and will fail the test if the two values are not equal.

We will explore much more about JUnit 4 and how to write more elaborate tests much later in the book.

How You Run Them

For a single test method or function, you will notice a triangle-pair “run” icon in the “gutter” area of the code editor:

Android Studio, Showing Instrumented Test in Kotlin
Android Studio, Showing Instrumented Test in Kotlin

Clicking that will allow you to run that individual test method/function. Or, optionally, you will be able to debug that test method.

Android Studio, Showing Pop-Up Menu for Running a Test Function
Android Studio, Showing Pop-Up Menu for Running a Test Function

Similarly, there is a “run” icon in the gutter next to the class name, to run all of the test functions in the Kotlin test class. For a Java class, the class-level test icon is a “double-run” pair of overlapping green triangles:

Android Studio, Showing Instrumented Test in Java
Android Studio, Showing Instrumented Test in Java

(Why is there a difference? Ask Google.)

For smaller projects, those may suffice. For larger projects, you can set up a custom “run configuration” that can run all of the instrumented tests in your project, for example. We will see this much later in the book.

What the Test Results Look Like

When you run a test method or a test class, rather than focusing on the output in the emulator or device, you will focus instead on the “Run” view in Android Studio. This will show you which tests succeeded and which tests failed.

The nice people who created Android Studio elected to write tests that succeed, and Android Studio’s output will reflect that:

Android Studio, Showing Successful Test Results
Android Studio, Showing Successful Test Results

Note the “All Tests Passed” message with the green checkmark-in-circle icon.

If a test fails — such as a modified version of the sample’s test class that compares the application ID to this.is.wrong — you will see the failed test in a tree on the left and details of what went wrong on the right:

Android Studio, Showing Failed Test Results
Android Studio, Showing Failed Test Results

JUnit assertions, such as assertEquals(), will provide some details as to what went wrong, shown in a stack trace in the test output:

org.junit.ComparisonFailure: expected:<[this.is.wrong]> but was:<[com.commonsware.jetpack.hello]>

Here, we see that we expected this.is.wrong, but we instead got com.commonsware.jetpack.hello, and so the test failed.

About That testInstrumentationRunner

Back in the chapter on Gradle, we saw this line in the defaultConfig closure:

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

The testInstrumentationRunner indicates what code should be used to execute the JUnit tests themselves. The runner shown here is the standard runner for instrumented tests. Various third-party testing tools might have you replace this value with some class from their library, so this is not always the test runner that we use.

The androidTestImplementation Dependencies

Such a library would also show up as an androidTestImplementation dependency in the list of dependencies for the module. We have a couple of those already:

  androidTestImplementation 'androidx.test.ext:junit:1.1.3'
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

The one that is really required is androidx.test:runner. This is the library that supplies the AndroidJUnitRunner class and other core classes for writing and running our instrumented tests.

The other one is androidx.test.espresso:espresso-core. This is the core of Espresso, a powerful library for writing GUI tests. However, our existing instrumented test does not actually use Espresso, so this particular dependency is unnecessary at the moment. We will cover Espresso much later in the book.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.