Writing An Integration Test With Jetpack Compose and Dagger Hilt
This post will show a simple setup for running an integration test between Compose and a ViewModel using Hilt.
Since Compose turned beta, I’ve been spending time getting familiar with it. I chose a side project as my learning medium — a simple app that displays a list of inventory items. Being a “responsible” engineer, I decided to add tests from the initial stages. Compose and Hilt's teams have done an excellent job documenting them, so information regarding testing is easily found. However, I didn’t find much on a scenario involving both.
Project Structure
Let’s assume the following structure.
The Repository, Viewmodel and Activity are wired together using Hilt to manage their dependencies.
Test Scenario
For this article, we’ll go with a simple integration test scenario, verifying our UI displays a list of inventory items. Ideally, this test helps us ensure that our ViewModel and Compose UI are working together as intended.
(Please remember this is a contrived example. An argument could be made regarding the value of such a test. However, that’s not the focus of this post)
Let’s take a closer look at some of the code in our project to understand better how to achieve this test.
The Code Under Test
(This article won’t go into details explaining how Hilt works, however, please note the annotations @AndroidEntryPoint
, @HiltViewModel
, @Inject
are all related to the Hilt setup required to use it for dependency injection. More info here)
Looking at the code snippets above, we can observe the InventoryViewModel
depends on the InventoryRepository
for data. The repository would typically be responsible for fetching this data from the cloud.
When testing, we aim to create a scenario that is as close to production as possible while still being predictable and reliable. Therefore, it’s in our best interest to avoid hitting any actual servers for data. Instead, we’d prefer to provide a hard-coded list of items for test assertions.
Taking a closer look at the InventoryRepository
, we see that it’s an interface. This is significant because it gives us the flexibility to provide any concrete implementation we like.
However, we would like the swap to only occur while testing. This is where Hilt and its testing capabilities come into the picture. We’ll see what those are in a few moments.
To accomplish our testing goal, we’ll require the following:
- A predetermined list of items used for testing
- Test dependencies for Hilt: Hilt provides API’s that allow us to manipulate the dependency injection process to suit our test needs. We can achieve this with the
HiltAndroidRule, HiltTestApplication
andHiltTestRunner
- Test dependencies for Compose: This provides the
ComposeTestRule
which we’ll use to “load” our composable and perform assertions. - Patience 😂
Let’s get to it…
Adding the test dependencies
The following dependencies give us access to the test rules and other components for writing our test.
Providing the fake list of items
Hilt makes it pretty easy to do this. The basic premise is to substitute a real dependency with a test version by replacing the module used to provide that dependency.
This code basically results in the creation of the module applicable only during testing. Using the @TestInstallIn
annotation provided by Hilt’s test artefacts, we can specify:
- the relevant component scope for the dependency. This is done using the
components
parameter, - the module that should be replaced by this test module using the
replaces
parameter.
Installing the HiltTestApplication
Using Hilt requires an application class annotated with @HiltAndroidApp
. This annotation generates a top-level component used by Hilt to provide dependencies to other components (e.g. The ActivityComponent
created by the @androidEntryPoint
annotation.)
The same requirement applies during testing. Fortunately, Hilt provides a test application we can use and here’s how the setup looks.
We’ll use this test runner in place of the default test runner by specifying this line in our project-level Gradle file.
Finally, write the test.
In this last step, we’re finally ready to write the test class.
There are a few things we should be aware of while writing the test:
- To run an instrumentation test with Hilt, we’ll annotate the test class with
@HiltAndroidTest
. - The
HiltAndroidRule
allows us to perform injections within the test by callinginject()
. This will enable us to replace the ViewModel’s repository dependency with the fake we defined earlier. - The
ComposeTestRule
is necessary to load the main(root) composable onto the screen. This is done by callingsetContent
on the test rule and passing in the composable.
Ordering the test rules
To successfully write a functioning UI test using both Compose and Hilt, both test rules must be accessible as class-level properties to allow access to the test API’s mentioned above. Additionally, Hilt requires its test rule to be executed first. JUnit provides two approaches for accomplishing this.
- RuleChain API: Using this API, we could easily specify the order like this:
However, that would prevent the rules from being accessible as individual properties, thus keeping us from calling inject()
and setContent{}
on the Hilt and Compose test rules, respectively. Therefore, this (👇🏽) is a better approach.
- The
order
parameter: JUnit4.13
provides an even simpler way to achieve these constraints by using theorder
parameter of theRule
annotation. All you need to do is provide a numerical value for each rule. The provided numerical values are used to sort the rules in ascending order. Here’s what the code would look like using this approach:
Putting everything together, we’ll end up with this trivial but functional integration test.
Following the steps described above in our simplified scenario, you should be able to write an integration test for your Composed based UI’s with Hilt injected dependencies. The key takeaways are to ensure both test rules are accessible as class-level properties within the test and to use Hilt’s mechanisms for providing fake implementations while testing.
Happy Composing and Testing.
I want to thank Jossi Wolf, Hannes Struß and Lisa Onwordi for their feedback while writing this article.