1. Welcome
Introduction
When you implemented the first feature of your first app, you likely ran the code to verify that it worked as expected. You performed a test, albeit a manual test. As you continued to add and update features, you probably also continued to run your code and verify it works. But doing this manually every time is tiring, prone to mistakes, and does not scale.
Computers are great at scaling and automation! So developers at companies large and small write automated tests, which are tests that are run by software and do not require you to manually operate the app to verify the code works.
What you'll learn in this series of codelabs is how to create a collection of tests (known as a testing suite) for a real-world app.
This first codelab covers the basics of testing on Android, you'll write your first tests and learn how to test LiveData and ViewModels.
What you should already know
You should be familiar with:
- The following core Android Jetpack libraries:
ViewModelandLiveData - Application architecture, following the pattern from the Guide to app architecture and Android Fundamentals codelabs
What you'll learn
You'll learn about the following topics:
- How to write and run unit tests on Android
- How to use Test Driven Development
- How to choose instrumented tests and local tests
You'll learn about the following libraries and code concepts:
What you'll do
- Set up, run, and interpret both local and instrumented tests in Android.
- Write unit tests in Android using JUnit4 and Hamcrest.
- Write simple
LiveDataandViewModeltests.
2. App overview
In this series of codelabs, you'll be working with the TO-DO Notes app. The app allows you to write down tasks to complete and displays them in a list. You can then mark them as completed or not, filter them, or delete them.

This app is written in Kotlin, has several screens, uses Jetpack components, and follows the architecture from a Guide to app architecture. By learning how to test this app, you'll be able to test apps that use the same libraries and architecture.
3. Getting Started
To get started, download the code:
Alternatively, you can clone the Github repository for the code:
$ git clone https://github.com/google-developer-training/advanced-android-testing.git $ cd android-testing $ git checkout starter_code
You can browse the code in the android-testing Github repository.
4. Task: Familiarizing yourself with the code
In this task you'll run the app and explore the code base.
Step 1: Run the sample app
Once you've downloaded the TO-DO app, open it in Android Studio and run it. It should compile. Explore the app by doing the following:
- Create a new task with the plus floating action button. Enter a title first, then enter additional information about the task. Save it with the green check FAB.
- In the list of tasks, click on the title of the task you just completed and look at the detail screen for that task to see the rest of the description.
- In the list or on the detail screen, check the checkbox of that task to set its status to Completed.
- Go back to the tasks screen, open the filter menu, and filter the tasks by Active and Completed status.
- Open the navigation drawer and click Statistics.
- Got back to the overview screen, and from the navigation drawer menu, select Clear completed to delete all tasks with the Completed status

Step 2: Explore the sample app code
The TO-DO app is based off of the Architecture Blueprints testing and architecture sample. The app follows the architecture from a Guide to app architecture. It uses ViewModels with Fragments, a repository, and Room. If you're familiar with any of the below examples, this app has a similar architecture:
- Android Kotlin Fundamentals training codelabs
- Advanced Android training codelabs
- Room with a View Codelab
- Android Sunflower Sample
- Developing Android Apps with Kotlin Udacity training course
It is more important that you understand the general architecture of the app than have a deep understanding of the logic at any one layer.

Here's the summary of packages you'll find:
Package: | ||
| The add or edit a task screen: UI layer code for adding or editing a task. | |
| The data layer: This deals with the data layer of the tasks. It contains the database, network, and repository code. | |
| The statistics screen: UI layer code for the statistics screen. | |
| The task detail screen: UI layer code for a single task. | |
| The tasks screen: UI layer code for the list of all tasks. | |
| Utility classes: Shared classes used in various parts of the app, e.g. for the swipe refresh layout used on multiple screens. | |
Data layer (.data)
This app includes a simulated networking layer, in the remote package, and a database layer, in the local package. For simplicity, in this project the networking layer is simulated with just a HashMap with a delay, rather than making real network requests.
The DefaultTasksRepository coordinates or mediates between the networking layer and the database layer and is what returns data to the UI layer.
UI layer ( .addedittask, .statistics, .taskdetail, .tasks)
Each of the UI layer packages contains a fragment and a view model, along with any other classes that are required for the UI (such as an adapter for the task list). The TaskActivity is the activity that contains all of the fragments.
Navigation
Navigation for the app is controlled by the Navigation component. It is defined in the nav_graph.xml file. Navigation is triggered in the view models using the Event class; the view models also determine what arguments to pass. The fragments observe the Events and do the actual navigation between screens.
5. Task: Running tests
In this task, you'll run your first tests.
- In Android Studio, open up the Project pane and find these three folders:
com.example.android.architecture.blueprints.todoappcom.example.android.architecture.blueprints.todoapp (androidTest)com.example.android.architecture.blueprints.todoapp (test)
These folders are known as source sets. Source sets are folders containing source code for your app. The source sets, which are colored green (androidTest and test) contain your tests. When you create a new Android project, you get the following three source sets by default. They are:
main: Contains your app code. This code is shared amongst all different versions of the app you can build (known as build variants)androidTest: Contains tests known as instrumented tests.test: Contains tests known as local tests.
The difference between local tests and instrumented tests is in the way they are run.
Local tests (test source set)
These tests are run locally on your development machine's JVM and do not require an emulator or physical device. Because of this, they run fast, but their fidelity is lower, meaning they act less like they would in the real world.
In Android Studio local tests are represented by a green and red triangle icon.

Instrumented tests (androidTest source set)
These tests run on real or emulated Android devices, so they reflect what will happen in the real world, but are also much slower.
In Android Studio instrumented tests are represented by an Android with a green and red triangle icon.

Step 1: Run a local test
- Open the
testfolder until you find the ExampleUnitTest.kt file. - Right-click on it and select Run ExampleUnitTest.
You should see the following output in the Run window at the bottom of the screen:

- Notice the green checkmarks and expand the test results to confirm that one test called
addition_isCorrectpassed. It's good to know that addition works as expected!
Step 2: Make the test fail
Below is the test that you just ran.
ExampleUnitTest.kt
// A test class is just a normal class
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
// Here you are checking that 4 is the same as 2+2
assertEquals(4, 2 + 2)
}
}
Notice that tests
- are a class in one of the test source sets.
- contain functions that start with the
@Testannotation (each function is a single test). - usually contain assertion statements.
Android uses the testing library JUnit for testing (in this codelab JUnit4). Both assertions and the @Test annotation come from JUnit.
An assertion is the core of your test. It's a code statement that checks that your code or app behaved as expected. In this case, the assertion is assertEquals(4, 2 + 2) which checks that 4 is equal to 2 + 2.
To see what a failed test looks like add an assertion that you can easily see should fail. It'll check that 3 equals 1+1.
- Add
assertEquals(3, 1 + 1)to theaddition_isCorrecttest.
ExampleUnitTest.kt
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
assertEquals(3, 1 + 1) // This should fail
}
}
- Run the test.
- In the test results, notice an X next to the test.

- Also notice:
- A single failed assertion fails the entire test.
- You are told the expected value (3) versus the value that was actually calculated (2).
- You are directed to the line of the failed assertion
(ExampleUnitTest.kt:16).
Step 3: Run an instrumented test
Instrumented tests are in the androidTest source set.
- Open the
androidTestsource set. - Run the test called
ExampleInstrumentedTest.
ExampleInstrumentedTest
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.android.architecture.blueprints.reactive",
appContext.packageName)
}
}
Unlike the local test, this test runs on a device (in the example below an emulated Pixel 2 phone):

If you have a device attached or an emulator running, you should see the test run on the emulator.
6. Task: Writing your first test
In this task, you'll write tests for getActiveAndCompleteStats, which calculates the percentage of active and complete task stats for your app. You can see these numbers on the statistics screen of the app.

Step 1: Create a test class
- In the
mainsource set, intodoapp.statistics, openStatisticsUtils.kt. - Find the
getActiveAndCompletedStatsfunction.
StatisticsUtils.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)
The getActiveAndCompletedStats function accepts a list of tasks and returns a StatsResult. StatsResult is a data class that contains two numbers, the percentage of tasks that are completed, and the percentage that are active.
Android Studio gives you tools to generate test stubs to help you implement the tests for this function.
- Right click
getActiveAndCompletedStatsand select Generate > Test.
|
|
The Create Test dialog opens:

- Change the Class name: to
StatisticsUtilsTest(instead ofStatisticsUtilsKtTest; it's slightly nicer not to have KT in the test class name). - Keep the rest of the defaults. JUnit 4 is the appropriate testing library. The destination package is correct (it mirrors the location of the
StatisticsUtilsclass) and you don't need to check any of the check boxes (this just generates extra code, but you'll write your test from scratch). - Press OK.
The Choose Destination Directory dialog opens: 
You'll be making a local test because your function is doing math calculations and won't include any Android specific code. So, there's no need to run it on a real or emulated device.
- Select the
testdirectory (notandroidTest) because you'll be writing local tests. - Click OK.
- Notice the generated the
StatisticsUtilsTestclass intest/statistics/.

Step 2: Write your first test function
You're going to write a test that checks:
- if there are no completed tasks and one active task,
- that the percentage of active tests is 100%,
- and the percentage of completed tasks is 0%.
- Open
StatisticsUtilsTest. - Create a function named
getActiveAndCompletedStats_noCompleted_returnsHundredZero.
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- Add the
@Testannotation above the function name to indicate it's a test. - Create a list of tasks.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- Call
getActiveAndCompletedStatswith these tasks.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- Check that
resultis what you expected, using assertions.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
Here is the complete code.
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
}
}
- Run the test (Right click
StatisticsUtilsTestand select Run).
It should pass:

Step 3: Add the Hamcrest dependency
Because your tests act as documentation of what your code does, it's nice when they are human readable. Compare the following two assertions:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
The second assertion reads much more like a human sentence. It is written using an assertion framework called Hamcrest. Another good tool for writing readable assertions is the Truth library. You'll be using Hamcrest in this codelab to write assertions.
- Open
build.grade (Module: app)and add the following dependency.
app/build.gradle
dependencies {
// Other dependencies
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}
Usually, you use implementation when adding a dependency, yet here you're using testImplementation. When you're ready to share your app with the world, it is best not to bloat the size of your APK with any of the test code or dependencies in your app. You can designate whether a library should be included in the main or test code by using gradle configurations. The most common configurations are:
implementation—The dependency is available in all source sets, including the test source sets.testImplementation—The dependency is only available in the test source set.androidTestImplementation—The dependency is only available in theandroidTestsource set.
Which configuration you use, defines where the dependency can be used. If you write:
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
This means that Hamcrest will only be available in the test source set. It also ensures that Hamcrest will not be included in your final app.
Step 4: Use Hamcrest to write assertions
- Update the
getActiveAndCompletedStats_noCompleted_returnsHundredZero()test to use Hamcrest'sassertThatinstead ofassertEquals.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
Note you can use the import import org.hamcrest.Matchers.is`` if prompted.
The final test will look like the code below.
StatisticsUtilsTest.kt
import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active tasks (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
- Run your updated test to confirm it still works!
This codelab will not teach you all the ins and outs of Hamcrest, so if you'd like to learn more check out the official tutorial.
subjectUnderTest_actionOrInput_resultState
- Subject under test is the method or class that is being tested (
getActiveAndCompletedStats). - Next is the action or input (
noCompleted). - Finally you have the expected result (
returnsHundredZero).
7. Task: Writing more tests
This is an optional task for practice.
In this task, you'll write more tests using JUnit and Hamcrest. You'll also write tests using a strategy derived from the program practice of Test Driven Development. Test Driven Development or TDD is a school of programming thought that says instead of writing your feature code first, you write your tests first. Then you write your feature code with the goal of passing your tests.
Step 1. Write the tests
Write tests for when you have a normal task list:
- If there is one completed task and no active tasks, the
activeTaskspercentage should be0f, and the completed tasks percentage should be100f. - If there are two completed tasks and three active tasks, the completed percentage should be
40fand the active percentage should be60f.
Step 2. Write a test for a bug
The code for the getActiveAndCompletedStats as written has a bug. Notice how it does not properly handle what happens if the list is empty or null. In both of these cases, both percentages should be zero.
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
To fix the code and write tests, you'll use test driven development. Test Driven Development follows these steps.
- Write the test, using the Given, When, Then structure, and with a name that follows the convention.
- Confirm the test fails.
- Write the minimal code to get the test to pass.
- Repeat for all tests!

Instead of starting by fixing the bug, you'll start by writing the tests first. Then you can confirm that you have tests protecting you from ever accidentally reintroducing these bugs in the future.
- If there is an empty list (

