1. Welcome
Introduction
This third testing codelab is a survey of additional testing topics, including:
- Coroutines, including view model scoped coroutines
- Room
- Databinding
- End-to-End tests
What you should already know
You should be familiar with:
- Testing concepts covered in the 5.1 Testing Basics and 5.2 Dependency Injection and Test Doubles codelabs: Writing and running unit tests on Android, using JUnit, Hamcrest, AndroidX test, Robolectric, Testing LiveData, manual Dependency Injection, Test Doubles (mocks and fakes), Service Locators, testing the Navigation Component, and Espresso.
- The following core Android Jetpack libraries:
view model,LiveData, Data Binding, and the Navigation Component - Application architecture, following the pattern from the Guide to app architecture and Android Fundamentals codelabs.
- The basics of coroutines (including
ViewModelScope) on Android.
What you'll learn
- How to test coroutines, including view model scoped coroutines.
- How to test simple error edge cases.
- How to test Room.
- How to test data binding with Espresso.
- How to write end-to-end tests.
- How to test global app navigation.
You will use:
runBlockingandrunBlockingTestTestCoroutineDispatcherpauseDispatcherandresumeDispatcherinMemoryDatabaseBuilderIdlingResource
What you'll do
- Write
ViewModelintegration tests that test code usingviewModelScope. - Pause and resume coroutine execution for testing.
- Modify a fake repository to support error testing.
- Write DAO unit tests.
- Write local data source integration tests.
- Write end-to-end tests that include coroutine and data binding code.
- Write global app navigation tests.
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 a few screens, uses Jetpack components, and follows the architecture from a Guide to app architecture. Learning how to test this app will enable you to test apps that use the same libraries and architecture.
Download the Code
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 end_codelab_2
Take a moment to familiarize yourself with the code, following the instructions below.
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.
3. Task: Introduction to and review of testing coroutines
Code executes either synchronously or asynchronously.
- When code is running synchronously, a task completely finishes before execution moves to the next task.
- When code is running asynchronously, tasks run in parallel.
|
|
Asynchronous code is almost always used for long-running tasks, such as network or database calls. It can also be difficult to test. There are two common reasons for this:
- Asynchronous code tends to be non-deterministic. What this means is that if a test runs operations A and B in parallel, multiple times, sometimes A will finish first, and sometimes B. This can cause flaky tests (tests with inconsistent results).

- When testing, you often need to ensure some sort of synchronization mechanism for asynchronous code. Tests run on a testing thread. As your test runs code on different threads, or makes new coroutines, this work is started asynchronously, seperate from the test thread. Meanwhile the test coroutine will keep executing instructions in parallel. The test might finish before either of the fired-off tasks finish.
Synchronization mechanisms are ways to tell the test execution to "wait" until the asynchronous work finishes.

In Kotlin, a common mechanism for running code asynchronously is coroutines. When testing asynchronous code, you need to make your code deterministic and provide synchronization mechanisms. The following classes and methodologies help with that:
- Using
runBlockingTestorrunBlocking. - Using
TestCoroutineDispatcherfor local tests. - Pausing coroutine execution to test the state of the code at an exact place in time.
You will start by exploring the difference between runBlockingTest and runBlocking.
Step 1: Observe how to run basic coroutines in tests
To test code that includes suspend functions, you need to do the following:
- Add the
kotlinx-coroutines-testtest dependency to your app's build.gradle file. - Annotate the test class or test function with
@ExperimentalCoroutinesApi. - Surround the code with
runBlockingTest, so that your test waits for the coroutine to finish.
Let's look at an example.
- Open your app's
build.gradlefile. - Find the
kotlinx-coroutines-testdependency (this is provided for you):
app/build.gradle
// Dependencies for Android instrumented unit tests
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
kotlinx-coroutines-test is an experimental library for testing coroutines. It includes utilities for testing coroutines, including runBlockingTest.
You must use runBlockingTest whenever you want to run a coroutine from a test. Usually, this is when you need to call a suspend function from a test.
- Take a look at this example from TaskDetailFragmentTest.kt. Pay attention to the lines that say
//LOOK HERE:
TaskDetailFragmentTest.kt
@MediumTest
@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi // LOOK HERE
class TaskDetailFragmentTest {
//... Setup and teardown
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{ // LOOK HERE
// GIVEN - Add active (incomplete) task to the DB.
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask) // LOOK HERE Example of calling a suspend function
// WHEN - Details fragment launched to display task.
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
// THEN - Task details are displayed on the screen.
// Make sure that the title/description are both shown and correct.
// Lots of Espresso code...
}
// More tests...
}
To use runBlockingTest you:
- Annotate the function or class with
@ExperimentalCoroutinesApi. - Wrap the code calling the suspend function with
runBlockingTest.
When you use any functions from kotlinx-coroutines-test, annotate the class or function with @ExperimentalCoroutinesApi since kotlinx-coroutines-test is still experimental and the API might change. If you don't do this, you'll get a lint warning.
runBlockingTest is used in the above code because you are calling repository.saveTask(activeTask), which is a suspend function.
runBlockingTest handles both running the code deterministically and providing a synchronization mechanism. runBlockingTest takes in a block of code and blocks the test thread until all of the coroutines it starts are finished. It also runs the code in the coroutines immediately (skipping any calls to delay) and in the order they are called–-in short, it runs them in a deterministic order.
runBlockingTest essentially makes your coroutines run like non-coroutines by giving you a coroutine context specifically for test code.
You do this in your tests because it's important that the code runs in the same way every single time (synchronous and deterministic).
Step 2: Observe runBlocking in Test Doubles
There is another function, runBlocking, which is used when you need to use coroutines in your test doubles as opposed to your test classes. Using runBlocking looks very similar to runBlockingTest, as you wrap it around a code block to use it.
- Take a look at this example from FakeTestRepository.kt. Note that since
runBlockingis not part of thekotlinx-coroutines-testlibrary, you do not need to use theExperimentalCoroutinesApiannotation.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private val observableTasks = MutableLiveData<Result<List<Task>>>()
// More code...
override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() } // LOOK HERE
return observableTasks
}
override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}
// More code...
}
Similar to runBlockingTest, runBlocking is used here because refreshTasks is a suspend function.
runBlocking vs. runBlockingTest
Both runBlocking and runBlockingTest block the current thread and wait until any associated coroutines launched in the lambda complete.
In addition, runBlockingTest has the following behaviors meant for testing:
- It skips
delay, so your tests run faster. - It adds testing related assertions to the end of the coroutine. These assertions fail if you launch a coroutine and it continues running after the end of the
runBlockinglambda (which is a possible coroutine leak) or if you have an uncaught exception. - It gives you timing control over the coroutine execution.
So why use runBlocking in your test doubles, like FakeTestRepository? Sometimes you will need a coroutine for a test double, in which case you do need to block the current thread. This is, so that when your test doubles are used in a test case, the thread blocks and allows the coroutine to finish before the test does. Test doubles, though, aren't actually defining a test case, so they don't need and shouldn't use all of the test specific features of runBlockingTest.
In summary:
- Tests require deterministic behavior so they aren't flaky.
- "Normal" coroutines are non-deterministic because they run code asynchronously.
kotlinx-coroutines-testis the gradle dependency forrunBlockingTest.- Writing test classes, meaning classes with
@Testfunctions, userunBlockingTestto get deterministic behavior. - Writing test doubles, use
runBlocking.
4. Task: Coroutines and ViewModels
In this step you'll learn how to test view models that use coroutines.
All coroutines require a CoroutineScope. Coroutine scopes control the lifetimes of coroutines. When you cancel a scope (or technically, the coroutine's Job, which you can learn more about here), all of the coroutines running in the scope are cancelled.
Since you might start long running work from a view model, you'll often find yourself creating and running coroutines inside view models. Normally, you'd need to create and configure a new CoroutineScope manually for each view model to run any coroutines. This is a lot of boilerplate code. To avoid this, lifecycle-viewmodel-ktx provides an extension property called viewModelScope.
viewModelScope is a CoroutineScope associated with each view model. viewModelScope is configured for use in that particular ViewModel. What this means specifically is that:
- The
viewModelScopeis tied to the view model such that when the view model is cleaned up (i.e.onClearedis called), the scope is cancelled. This ensures that when your view model goes away, so does all the coroutine work associated with it. This avoids wasted work and memory leaks. - The
viewModelScopeuses theDispatchers.Maincoroutine dispatcher. ACoroutineDispatchercontrols how a coroutine runs, including what thread the coroutine code runs on.Dispatcher.Mainputs the coroutine on the UI or main thread. This makes sense as a default forViewModelcoroutines, because often, view models manipulate the UI.
This works well in production code. But for local tests (tests that run on your local machine in the test source set), the usage of Dispatcher.Main causes an issue: Dispatchers.Main uses Android's Looper.getMainLooper(). The main looper is the execution loop for a real application. The main looper is not available (by default) in local tests, because you're not running the full application.
To address this, use the method setMain() (from kotlinx.coroutines.test) to modify Dispatchers.Main to use TestCoroutineDispatcher. TestCoroutineDispatcher is a dispatcher specifically meant for testing.
Next, you will write tests for view model code that uses viewModelScope.
Step 1: Observe Dispatcher.Main causing an error
Add a test which checks that when a task is completed, the snackbar shows the correct completion message.
- Open test > tasks > TasksViewModelTest.

- Add this new test method:
TasksViewModelTest.kt
@Test
fun completeTask_dataAndSnackbarUpdated() {
// Create an active task and add it to the repository.
val task = Task("Title", "Description")
tasksRepository.addTasks(task)
// Mark the task as complete task.
tasksViewModel.completeTask(task, true)
// Verify the task is completed.
assertThat(tasksRepository.tasksServiceData[task.id]?.isCompleted, `is`(true))
// Assert that the snackbar has been updated with the correct text.
val snackbarText: Event<Int> = tasksViewModel.snackbarText.getOrAwaitValue()
assertThat(snackbarText.getContentIfNotHandled(), `is`(R.string.task_marked_complete))
}
- Run this test. Observe that it fails with the following error:
"Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used."
This error states that the Dispatcher.Main has failed to initialize. The underlying reason (not explained in the error) is the lack of Android's Looper.getMainLooper(). The error message does tell you to use Dispatcher.setMain from kotlinx-coroutines-test. Go ahead and do just that!
Step 2: Replace Dispatcher.Main with TestCoroutineDispatcher
TestCoroutineDispatcher is a coroutine dispatcher meant for testing. It executes tasks immediately and gives you control over the timing of coroutine execution in tests, such as allowing you to pause and restart coroutine execution.
- In TasksViewModelTest, create a
TestCoroutineDispatcheras avalcalledtestDispatcher.
Use testDispatcher instead of the default Main dispatcher.
- Create a
@Beforemethod that callsDispatchers.setMain(testDispatcher)before every test. - Create an
@Aftermethod that cleans everything up after running each test by callingDispatchers.resetMain()and thentestDispatcher.cleanupTestCoroutines().
Here's what this code looks like:
TasksViewModelTest.kt
@ExperimentalCoroutinesApi
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
@ExperimentalCoroutinesApi
@Before
fun setupDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@ExperimentalCoroutinesApi
@After
fun tearDownDispatcher() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
- Run your test again. It now passes!
Step 3: Add MainCoroutineRule
If you're using coroutines in your app, any local test that involves calling code in a view model is highly likely to call code which uses viewModelScope. Instead of copying and pasting the code to set up and tear down the TestCoroutineDispatcher into each test class, you can make a custom JUnit rule to avoid this boilerplate code.
JUnit rules are classes where you can define generic testing code that can execute before, after, or during a test–-it's a way to take your code that would have been in @Before and @After, and put it in a class where it can be reused.
Make a JUnit rule now.
- Create a new class called MainCoroutineRule.kt in the root folder of the test source set:

- Copy the following code to
MainCoroutineRule.kt:
MainCoroutineRule.kt
@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
Some things to notice:
MainCoroutineRuleextendsTestWatcher, which implements theTestRuleinterface. This is what makesMainCoroutineRulea JUnit rule.- The
startingandfinishedmethods match what you wrote in your@Beforeand@Afterfunctions. They also run before and after each test. MainCoroutineRulealso implementsTestCoroutineScope, to which you pass in theTestCoroutineDispatcher. This givesMainCoroutineRulethe ability to control coroutine timing (using theTestCoroutineDispatcheryou pass in). You'll see an example of this in the next step.
Step 4: Use your new Junit rule in a test
- Open TasksViewModelTest.
- Replace
testDispatcherand your@Beforeand@Aftercode with the newMainCoroutineRuleJUnit rule:
TasksViewModelTest.kt
// REPLACE@ExperimentalCoroutinesApi
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
@ExperimentalCoroutinesApi
@Before
fun setupDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@ExperimentalCoroutinesApi
@After
fun tearDownDispatcher() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
// WITH
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
Notice: To use the JUnit rule, you instantiate the rule and annotate it with @get:Rule.
- Run
completeTask_dataAndSnackbarUpdated, and it should work exactly the same!
Step 5: Use MainCoroutineRule for repository testing
In the previous codelab, you learned about dependency injection. This allows you to replace the production versions of classes with test versions of classes in your tests. Specifically, you used constructor dependency injection. Here's an example from DefaultTasksRepository:
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) : TasksRepository { ... }
The above code injects a local and remote data source, as well as a CoroutineDispatcher. Because the dispatcher is injected, you can use TestCoroutineDispatcher in your tests. Injecting the CoroutineDispatcher, as opposed to hard coding the dispatcher, is a good habit when using coroutines.
Let's use the injected TestCoroutineDispatcher in your tests.
- Open test > data > source > DefaultTasksRepositoryTest.kt

- Add the
MainCoroutineRuleinside theDefaultTasksRepositoryTestclass:
DefaultTasksRepositoryTest.kt
// Set the main coroutines dispatcher for unit testing.
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
- Use
Dispatcher.Main, instead ofDispatcher.Unconfinedwhen defining your repository under test. Similar toTestCoroutineDispatcher,Dispatchers.Unconfinedexecutes tasks immediately. But, it doesn't include all of the other testing benefits ofTestCoroutineDispatcher, such as being able to pause execution:
DefaultTasksRepositoryTest.kt
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test.
tasksRepository = DefaultTasksRepository(
// HERE Swap Dispatcher.Unconfined
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Main
)
}
In the above code, remember that MainCoroutineRule swaps the Dispatcher.Main for a TestCoroutineDispatcher.
Generally, only

