Home Android & Kotlin Tutorials

Testing With Hilt Tutorial: UI and Instrumentation Tests

Learn how to get started with testing with Hilt by writing UI and instrumentation tests.

4.4/5 5 Ratings


  • Kotlin 1.5, Android 4.4, Android Studio 4.2

Dagger is one of the most popular libraries for dependency injection for Java and Kotlin. It allows you to define dependency relationships between different components declaratively, by decorating your code with annotations such as @Inject and @Component.

As valuable as it is, Dagger is not simple. Google is addressing this by working hard to make it more accessible. Hilt is here for that reason — to provide a simpler way to define dependency injection in Android.

You already learned how to migrate your app to Hilt in the Migrating From Dagger to Hilt tutorial. Now, you’ll learn about how testing with Hilt can make your life easier.

In this tutorial, you’ll learn:

  • The role of Dagger and Hilt in test implementation.
  • How to implement and run UI tests with Robolectric and Hilt.
  • How to simplify the implementation of instrumentation tests.
  • What @UninstallModules and @BindValue are and how to use them in your tests.

You learn these things by implementing tests for the RW News app.

Note: This tutorial is part of a series about Dagger. If you’re not familiar with Dagger, review these resources before continuing:

Now, it’s time to dive in!

Getting Started

Download and unzip the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the starter project, then build and run. You’ll see the following:

The RW News app at the start of the project

RW News app

This is RW News, a new version of the app you already met in the Migrating From Dagger to Hilt tutorial. The app currently doesn’t have any tests, so it’s time to add some.

Open the RW News project and look at the initial structure for the code:

RWNews — Initial code structure

RWNews’s Initial Code Structure.

Note: Files in androidTest don’t compile at the moment. Don’t worry, you’ll fix this later in the tutorial, when you implement the instrumentation tests.

These are the important folders in the project, along with what they contain:

  • androidTest: Instrumentation tests.
  • debug: Code and tests available in debug mode.
  • main: The main app code.
  • test: Unit and Robolectric tests.
  • testShared: Code you share between different types of tests. It currently contains the fakes you’ll use in this tutorial.

Implementing UI Tests With Hilt and Robolectric

Now, it’s time to to work on the interesting stuff! Your goal is to implement:

  1. UI tests with Robolectric.
  2. Instrumentation tests.
Note: What about unit tests? As you know, they’re a fundamental part of every app, but Dagger and Hilt don’t help in their implementation. What helps isn’t the specific dependency injection framework, but using dependency injection itself. Dagger and Hilt are actually useful when you implement a UI test using Robolectric.

Robolectric is a testing framework that lets you implement and run tests that depend on the Android environment without an actual implementation of the Android platform. This allows you to run UI tests on the JVM without creating instances of the Android emulator. Tests run more quickly and require fewer resources when you use Robolectric.

In this tutorial, you’ll create a test for MainActivity. Before doing that, it’s important to understand the role of Dagger and Hilt, which already help you create the dependency tree of the objects for your app.

When you run tests, you need to replace some of the objects with a fake implementation — and for that, you need a different dependency tree. As you’ll see soon, Hilt lets you replace objects you use for the app with different objects that you only use when running tests.

Setting up Robolectric

Before you can write a UI test, you need to configure Robolectric. Your first step is to add the dependencies for Robolectric’s Hilt testing library.

Open build.gradle from app and add the following definition:

// ...
dependencies {
  // ...
  // Hilt for Robolectric tests.
  testImplementation "com.google.dagger:hilt-android-testing:$hilt_android_version" // 1
  testImplementation "org.robolectric:robolectric:$robolectric_version" // 2
  kaptTest "com.google.dagger:hilt-android-compiler:$hilt_android_version" // 3

Here, you:

  1. Add the dependency to use Hilt testing. This is a definition for the test build type.
  2. For the same test build type, you add the dependency to Robolectric.
  3. Use kaptTest to install the annotation processor responsible for generating the testing code from the Hilt definition.

The version you use in the code above is the same as that of the main Hilt library. Also, note that you must install an annotation processor when testing with Hilt. That’s because you need that annotation processor to generate some code.

Creating the RoboMainActivityTest File

For an example of a UI test with Hilt and Roboletric, you’ll test MainActivity. Open MainActivity.kt in the ui package and look at its code:

@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() {

  lateinit var navigationHelper: NavigationHelper // 2

  override fun onCreate(savedInstanceState: Bundle?) {
    val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
    if (savedInstanceState == null) {
      navigationHelper.replace(R.id.anchor, NewsListFragment()) // 3

This is a very simple Activity where you:

  1. Use @AndroidEntryPoint to tag the class as an Hilt entry point.
  2. Define a property of type NavigationHelper, which you initialize using @Inject.
  3. Use navigationHelper to display NewsListFragment.

To test MainActivity‘s UI, you need to use NavigationHelper to display NewsListFragment when you launch MainActivity. This requires you to create a test class, which you’ll do next.

Implementing RoboMainActivityTest

Now you will create the test class and name it RoboMainActivityTest.

Open ui/MainActivity.kt. Now, put the cursor on the class name and press Option-Enter. You’ll see the following result:

Menu option to create a unit test

Creating a Unit Test.

Select Create test and press Enter to get the following dialog:

Configuring the test

Configuring the Test.

Select JUnit4 as your testing library. It’s important to note that you need to select test as the destination build type:

Robolectric UI Tests folder

Robolectric UI Tests Folder.

Initially, you’ll get an empty class:

class RoboMainActivityTest

It’s important to check that this file is among the source files for the test build type:

RoboMainActivityTest Location

RoboMainActivityTest Location.

Next, you’ll add some code to implement the test.

Creating the Skeleton Class for a UI Robolectric Test

Open your newly created RoboMainActivityTest.kt and replace its content with the following:

import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode

@HiltAndroidTest // 1
@Config(application = HiltTestApplication::class) // 2
@RunWith(RobolectricTestRunner::class) // 3
@LooperMode(LooperMode.Mode.PAUSED)  // 4
class RoboMainActivityTest {

    var hiltAndroidRule = HiltAndroidRule(this) // 5

    fun setUp() {
        hiltAndroidRule.inject() // 6

    fun whenMainActivityLaunchedNavigationHelperIsInvokedForFragment() { // 7

There are many important things to note here:

  1. You annotate the test class with @HiltAndroidTest. This is very important because it tells Hilt you need to create a dependency graph for the test that’s different from the one you use in the app.
  2. As you learned in the previous tutorials, the Application implementation for the app is the place where the construction of the dependency graph starts. Here, you use @HiltAndroidApp to tell Hilt what this implementation is, just as you did in RwNewsApplication.kt. But, as you just learned, tests require a different dependency graph with different objects that you create using a different Application implementation.

    Hilt already provides this with HiltTestApplication. By using @Config, you’re telling Hilt that the Application implementation to use for the tests is HiltTestApplication. As you’ll see later, you can achieve the same result with a configuration in a robolectric.properties file.

  3. Using @RunWith, you explicitly define RobolectricTestRunner as the TestRunner to be used in order to run the tests in this file.
  4. Threading during the execution of a test is a complex topic that would require its own tutorial to explain thoroughly. Here, you’re using @LooperMode(LooperMode.Mode.PAUSED) to basically run the Robolectric tests on the main thread.
  5. To create and destroy the Hilt-provided dependency graph for each test execution, you create an instance of HiltAndroidRule in hiltAndroidRule.
  6. You invoke inject() on hiltAndroidRule at the beginning of each test. As you’ll see later, this injects objects from the Hilt test dependency graph into the test itself.
  7. Of course, you need to create the test function with a name that explains what you’re actually testing. In this case, you’re just asserting something that is true to check that all the configurations are correct.

Now, you’re ready to run the test by selecting the green arrow:

Running RoboMainActivityTest

Running RoboMainActivityTest.

This gives you the following result:

RoboMainActivityTest Result

RoboMainActivityTest Result

Next, you’ll see how to configure Robolectric tests using a robolectric.properties file.

Configuring Robolectric With robolectric.properties

To configure the SDK version to use when running Robolectric tests,

  1. Right click the test directory and select New > Directory.

    Then select resources

  2. Right click the resources directory and select New > File.

    Then create a new file called robolectric.properties

You would have file inside the resource folder for the test build type, like this:

Robolectric Properties File.

Robolectric Properties File.

Now, add the following properties:


Here, you use sdk to choose the SDK version for the Robolectric tests. You use application to define the Application implementation to use for the tests.

Now, try out the different SDK versions and see how the test results change.

Implementing a Robolectric UI Test

In the previous section, you created an empty test to verify the Hilt configuration for Robolectric. Now, it’s time to create the actual test.

To do this, you need to:

  • Configure the ActivityScenario API to launch MainActivity.
  • Replace the existing NavigationHelper implementation with the fake one.
  • Implement the assertions that tell you whether the test is successful or not.

Code along to implement the Robolectric UI test.

Configuring ActivityScenario

To configure the ActivityScenario API, open RoboMainActivityTest.kt and replace the existing class code with this:

import androidx.test.ext.junit.rules.ActivityScenarioRule

@Config(application = HiltTestApplication::class)
class RoboMainActivityTest {

  @get:Rule(order = 0)
  var hiltAndroidRule = HiltAndroidRule(this)

  @get:Rule(order = 1) // 2
  var activityScenarioRule: ActivityScenarioRule<MainActivity> =
      ActivityScenarioRule(MainActivity::class.java) // 1

  fun setUp() {

  fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
    activityScenarioRule.scenario // 3

The Activity Scenario API allows you to launch an Activity during a test. To use it, you need to:

  1. Initialize a new JUnit rule of type ActivityScenarioRule<MainActivity> in activityScenarioRule.
  2. Remember that a JUnit rule is a smart way to create and initialize the execution environment of a test and release it when the test completes. If you have multiple rules, the order you execute them in is important. Here, you use order to ensure the rules run in the correct sequence. HiltAndroidRule needs to be the first rule to run, so you set order = 0. Note that JUnit added this attribute in version 4.13.1.
  3. To launch MainActivity, you simply access scenario in activityScenarioRule. Note how MainActivity is the one you set as the parameter type value in ActivityScenarioRule<MainActivity>.

Now, everything looks right, but running the test as usual in Android Studio gives you the following error:

kotlin.UninitializedPropertyAccessException: lateinit property navigationHelper has not been initialized
  at com.raywenderlich.rwnews.ui.MainActivity.onCreate(MainActivity.kt:57)

This is not your fault. :] There’s a bug that prevents you from running the test in Android Studio. Until a fix comes along, run the test by using the following command in the terminal:

./gradlew testDebugUnitTest --tests "*.RoboMainActivityTest.*"
Note: To run the test from the command line, you need to check that you’re using Java 8. At the moment, this doesn’t work with the more recent Java version.

Now, the test should run successfully, confirming that you correctly configured the UI test with Robolectric and Hilt. Your next step is to implement the actual test.

Replacing NavigationHelper With a Fake

This is the fun part of implementing tests with Hilt. The current test uses the same dependency graph as the app, but you want to replace NavigationHelper‘s implementation with a fake. This is where Hilt helps. It provides some new annotations, specifically:

  • @UninstallModules
  • @BindValue

As you know, a @Module tells Dagger how to provide an instance for a specific type. If you open ActivityModule, you can see the following definition for NavigationHelper:

@InstallIn(ActivityComponent::class) // HERE
interface ActivityModule {

  fun provideNavigationHelper(
    impl: NavigationHelperImpl
  ): NavigationHelper

As you can see, @InstallIn is used to install the bindings in the ActivityModule of the component for a specific scope. In this case, it’s ActivityComponent.

To replace the binding for NavigationHelper, you need to be able to uninstall it from the same component. To enable this, you’ll use @UninstallModules in your test.

Open RoboMainActivityTest.kt and add the following definition (adding the missing imports using IDE):

@Config(application = HiltTestApplication::class)
@UninstallModules(ActivityModule::class) // HERE
class RoboMainActivityTest {
  // ...

With this simple line of code, you remove the bindings you defined in ActivityModule. Of course, you need to provide an alternative binding for NavigationHelper.

Create a new file named FakeNavigationHelper.kt in the testShared > kotlin > fakes folder. Then, add the following code in the file:

import androidx.fragment.app.Fragment
import com.raywenderlich.rwnews.ui.navigation.NavigationHelper

class FakeNavigationHelper : NavigationHelper {

  data class NavigationInput(
      val anchorId: Int,
      val fragment: Fragment,
      val backStack: String?

  val replaceRequests = mutableListOf<NavigationInput>()

  override fun replace(anchorId: Int, fragment: Fragment, backStack: String?) {
    replaceRequests.add(NavigationInput(anchorId, fragment, backStack))

This is a simple fake NavigationHelper implementation. To add it to the dependency graph for RoboMainActivityTest, you just need to add the following declaration to the class (adding the missing imports using IDE):

class RoboMainActivityTest {
  // ...
  @BindValue // 1
  @JvmField // 2
  val navigator: NavigationHelper = FakeNavigationHelper()  // 3
  // ...

With this code, you:

  1. Use @BindValue to add the binding for FakeNavigationHelper to the dependency graph for the test.
  2. Use @JvmField to ask Kotlin to generate a navigator as a field without a getter and setter. This is a Kotlin annotation.
  3. Define the navigator field that will contain the instance of the binding for NavigationHelper. In practice, this has the same effect as using @Inject in a normal entry point.

It’s important to mention that, while @UninstallModules uninstalls all the bindings in the module you pass as an attribute value, @BindValue adds a single binding. Later, you’ll see how to replace a complete @Module.

Adding the Assertions for the UI Test

Now, you’re finally ready to implement RoboMainActivityTest with the actual test. Open RoboMainActivityTest.kt and replace the test function whenMainActivityLaunchedNavigationHelperIsInvokedForFragment inside the RoboMainActivityTest class with the following new function definition:

import com.google.common.truth.Truth.assertThat
import com.raywenderlich.rwnews.ui.list.NewsListFragment
import com.raywenderlich.rwnews.R

// Annotations
class RoboMainActivityTest {
  // ...
  fun whenMainActivityLaunchedNavigationHelperIsInvokedForFragment() {
    activityScenarioRule.scenario // 1
    val fakeHelper = navigator as FakeNavigationHelper // 2
    with(fakeHelper.replaceRequests[0]) { // 3
  // ...

In this code, you:

  1. Launch MainActivity, accessing the scenario property of activityScenarioRule.
  2. Access navigator after a cast to FakeNavigationHelper.
  3. Use the Truth library to verify that FakeNavigationHelper has been invoked with the expected parameter values.
    Note: You can also use any other assertion library that you prefer.

Now, you can finally run the test and check if it’s successful.

Congratulations! You’ve created your first UI test using Hilt and Robolectric. Along the way, you learned how to use @UninstallModules and @BindValue to adapt the dependency graph of the app to the specific test.

Implementing Instrumentation Tests With Hilt and Espresso

In the previous part of the tutorial, you created a UI test using Robolectric. Now, you’ll use Hilt and Espresso to implement a test that’s a bit more challenging. In particular, you’ll create an Espresso UI test for NewsListFragment. In this case, you have some more work to do, because you need to:

  • Add the Hilt dependencies for the instrumentation test.
  • Create an @AndroidEntryPoint activity to use as the container for the Fragment under test.
  • Implement a utility class to launch the Fragment under test into the Hilt-enabled Activity.
  • Create a custom AndroidJUnitRunner that uses HiltTestApplication instead of the one Android provides by default, then configure it in build.gradle.
  • Implement and run the instrumentation test.

Code along and everything will be fine. :]

Note: As mentioned at the beginning of the tutorial, don’t worry if the FragmentTestUtil.kt in the androidTest build type doesn’t compile at the moment. You’ll fix it very soon.

Adding Instrumentation Test Dependencies

To run UI test with Espresso and Hilt, you need to add the following dependencies to the build.gradle for the app:

// ...
dependencies {
  // Hilt for instrumented tests.
  androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_android_version" // 1
  kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_android_version" // 2
  // ...

Only two things to note here:

  1. You add the dependency to the Hilt library for the instrumentation test in the androidTest build type.
  2. For the same reason, you use kaptAndroidTest to install the annotation processor that’s responsible for generating the testing code from the Hilt definition in instrumented tests.

Now, Sync Project with Gradle files and you’re ready to start writing your Espresso test. Its dependencies are already in the project.

Creating an Activity Container for the Fragment to Test

Usually, before you test a Fragment, you first launch an Activity as its container. With Hilt, there’s a problem — if the Fragment is an @AndroidEntryPoint, the same must be true for the container Activity. If you just use ActivityScenario, this doesn’t happen automatically. That’s why you need to create an activity container.

Start by creating a new folder named kotlin in debug, then create a package named com.raywenderlich.rwnews inside. For your next step, create a new class in it named HiltActivityForTest.kt and add the following code:

import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint // HERE
class HiltActivityForTest : AppCompatActivity()

Here, you create HiltActivityForTest, only to be able to mark it with @AndroidEntryPoint as explained earlier.

Next, you need to tell the instrumentation environment to use HiltActivityForTest during the test. Create AndroidManifest.xml for debug, like this:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

      android:exported="false" />

At this point, your structure will look like the image below and the FragmentTestUtil.kt will build successfully. Yeah! :]

Debug Build Type.

Debug Build Type.

But what exactly is in FragmentTestUtil? You’ll explore that next.

Launching the Fragment Under Test

The starter project for RW News already contains FragmentTestUtil.kt. Open it and take a look at its code, specifically at the following signature:

inline fun <reified T : Fragment> launchFragmentInHiltContainer(
    fragmentArgs: Bundle? = null,
    @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
    crossinline action: Fragment.() -> Unit = {}
) {
  // ...

You’ll use launchFragmentInHiltContainer() to launch a Fragment in the context of HiltActivityForTest. Both are Hilt @AndroidEntryPoints.

Creating the Custom AndroidJUnitRunner

To run the UI test you implemented using Roboletric, you had to specify which TestRunner implementation to use. You did that by either using @Config in the test itself or application in robolectric.properties.

Now, you have to do the same for the instrumentation test — with the important difference that you also need to create a runner.

Create a new runner package under androidTest/java/com.raywenderlich.rwnews. After that, create HiltTestRunner.kt inside it and add the following code:

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class HiltTestRunner : AndroidJUnitRunner() {

    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(
            HiltTestApplication::class.java.name, // HERE

Here, you override newApplication and force Hilt to use HiltTestApplication as the Application implementation for the tests. This code tells Hilt to use a different dependency graph for your instrumentation tests.

For your next step, you need to tell Gradle to use this implementation in place of the existing one. Open build.gradle for the app and apply the following change:

// ...
android {
  defaultConfig {
    testInstrumentationRunner "com.raywenderlich.rwnews.runner.HiltTestRunner" // HERE
  // ...
// ...

Here, you replaced the existing value for testInstrumentationRunner with HiltTestRunner‘s fully qualified name.

Next, Sync Project with Gradle files. Once that successfully completes, it’s finally time to write the instrumentation test for NewsListFragment.

Implementing NewsListFragmentTest

To implement NewsListFragment‘s test, create a new file named NewsListFragmentTest.kt inside androidTest/java/com.raywenderlich.rwnews and add the following code (adding the missing imports using IDE):

@UninstallModules(AppModule::class) // HERE
class NewsListFragmentTest {

    var hiltAndroidRule = HiltAndroidRule(this)

    fun setUp() {

Here in this code block, @UninstallModules annotation is used to remove bindings defined via the AppModule class which contains bindings for NewsRepository and RwNewsLogger.

Next, in the same class file add the following code (adding the missing imports using IDE):

class NewsListFragmentTest {

    // Rule definition
    // Before tests setup

    @InstallIn(SingletonComponent::class) // 1
    object TestAppModule {

        fun provideNewsRepository(): NewsRepository { // 2
            return FakeNewsRepository().apply {
                insert(News(1, "First Title", "First Body"))
                insert(News(2, "Second Title", "Second Body"))
                insert(News(3, "Third Title", "Third Body"))

        fun provideNewsLogger(): RwNewsLogger = FakeNewsLogger() // 2

Here in this code block:

  1. Install TestAppModule to replace the previous bindings for the test.
  2. You install a local @Module for the test. It provides an instance of FakeNewsRepository, which you populate with some dummy data. You do the same with FakeNewsLogger.

Finally, in the same class file add the test code (adding the missing imports using IDE):

class NewsListFragmentTest {

  // Rule definition
  // Before tests setup
  // Module setup

    fun whenDisplayed_newsListFromRepoIsDisplayed() { // 1
        launchFragmentInHiltContainer<NewsListFragment>() // 2
        scrollAtAndCheckTestVisible(0, "First Title")
        scrollAtAndCheckTestVisible(1, "Second Title")
        scrollAtAndCheckTestVisible(2, "Third Title")

    fun scrollAtAndCheckTestVisible(position: Int, text: String) {

This test should be quite straightforward now, but here are a few things to note:

  1. Implement the test, asserting that the data from NewsRepository actually display in the RecyclerView in NewsListFragment.
  2. Use launchFragmentInHiltContainer() to launch NewsListFragment in the HiltActivityForTest you prepared earlier.

Now, you can simply run the test as usual. With a successful test, you’ll get something like this:

Successful Espresso test

Successful Espresso test

Implementing Tests to Help Structure the Code

In the previous test, you could have just replaced the binding for NewsRepository instead of the entire AppModule. You did it that way to see how to replace a @Module, but it proves how important it is to group bindings together depending on how you might change or replace them in a test.

Even if NewsRepository and RwNewsLogger have the same scope, you should still define them in different @Modules.

Where to Go From Here?

To see the final version of the RW News app, download the project by clicking the Download Materials button at the top or bottom of this tutorial.

Great job completing the tutorial! First, you learned how to configure your project to run a different kind of test using Hilt. You then implemented a UI test using Hilt with Robolectric. Finally, you configured your project to use Hilt to run instrumentation tests.

During this journey you also met and used @UninstallModules and @BindValue.

To learn more about dependency injection with Hilt, check out the Dependency Injection with Hilt: Fundamentals video course.

The Dagger by Tutorials book also provides an in-depth look at how to use Dagger and Hilt.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!




More like this