Dagger Hilt: Testing injected Android components with code coverage

Alex Vanyo
Livefront
Published in
4 min readNov 17, 2020

--

Dagger Hilt, JaCoCo, Robolectric, Espresso, oh my!

Dagger Hilt is Google’s new opinionated add-on for setting up Dagger 2 for Android. Through its tie-ins with androidx and preconfigured components, it should be able to meet the common dependency injection demands of most apps.

Google also lays out a testing philosophy that Hilt can help realize, using the real Dagger graph in tests. The goal of this article is to make that philosophy a reality, by providing all of the build script pieces and an inheritance trick needed to set up testing an Android project with code coverage using Hilt, Robolectric, Espresso and JaCoCo.

This article won’t explain how to write tests for Robolectric and Espresso, or how to use Hilt’s testing utilities, so refer to the documentation for those projects for examples.

Step 1: Configure JaCoCo

The necessary JaCoCo configuration looks basically the same as a normal Android JaCoCo setup. Since it’s not a trivial task to set up, here is a sample build configuration that creates separate test reports for each application variant while also allowing for exclusions:

Step 2: Setup Robolectric and Espresso

The next step is setting up Robolectric and Espresso. Together, these allow running UI-like tests in the JVM without needing an emulator.

Step 3a: Configure Hilt with the Gradle plugin

There are two options for setting up Hilt, with or without the accompanied Gradle Plugin. For completeness, this is the normal setup required for Hilt at runtime, without tests. In the project’s root build.gradle.kts, we’ll add the Gradle plugin:

And in the app/build.gradle.kts, we will apply the plugin and add the runtime dependencies:

Because the Hilt Gradle plugin performs a bytecode transformation, running Robolectric tests now will cause JaCoCo to complain that the execution data for any transformed class annotated with @AndroidEntryPoint doesn’t match. To get around this, we can “lift” the transformation to a subclass that contains no functional code that we care about, which allows the actual implementation to remain untouched and trackable by JaCoCo:

Another note: Currently, the hilt.enableTransformForLocalTests only works with Gradle, and does not work if running the tests through Android Studio directly (tracked by https://github.com/google/dagger/pull/1811 and https://issuetracker.google.com/37003772. To workaround this, we can avoid using the Grade Plugin entirely (see below).

Step 3b: Configure Hilt without the Gradle plugin

In lieu of using the Hilt Gradle plugin, the desired superclass for an entry point can be passed to the @AndroidEntryPoint, and then the entry point directly extends from the generated Hilt_* class. Using the generated code directly means the root build.gradle.kts can remain untouched, and the app/build.gradle.kts doesn’t apply the plugin:

Although we don’t need to workaround the bytecode transformation for JaCoCo when the Hilt Gradle plugin isn’t applied, there is still an issue with having to pass the superclass to @AndroidEntryPoint. If the superclass has generic type arguments, those can’t be specified in the annotation. The workaround for this is a similar “lift” for the transformation, to ensure that the direct superclass has no type arguments:

And that’s it! With Hilt, Robolectric, Espresso, and JaCoCo, we can test actual Android objects that are injected with Dagger with code coverage. This allows for an incredibly useful set of tests between pure unit tests and full UI tests, that ensure that an application’s actual Dagger graph and all of its resolved dependencies are performing as expected. Using tools like @UninstallModules, modules for the edges of an application (such as networking) can be replaced, allowing for fast, non-flaky tests that exercise a lot of code that may previously have gone untested.

Alex works at Livefront, where we like to test everything we possibly can.

Current Caveats:

There are a few shortcomings and limitations currently that might be fixed in the future. In no particular order, here are a few:

--

--