Writing Instrumented Tests Using Hilt And Jetpack Compose

Saqib
Jetpack Composers
6 min readFeb 10, 2023

--

In this story we will see how to write Instrumented Tests using Jetpack Compose and Hilt

Photo by Clément Hélardot on Unsplash

We always put a lot of effort in writing code to fulfil use-cases and features. It’s equally important to write tests to make sure features and App are behaving as expected, in order to write tests we must write testable code. I will write a another story explaining about how we can write testable code But one of the points is to use dependency injection, we are using Hilt in our example and Hilt makes testing easier providing many testing capabilities.

This story will not go into basics about how to integrate Hilt. There is nice official documentation available that you can read from Dependency Injection with Hilt.

For this article basic understanding of Jetpack Compose and Hilt is expected

Code Structure

Having a glance of code structure via diagram as below

MainActivity is setting its content as ArticlesScreen is using ArticlesViewModel is using ArticlesRepository and are wired together via Hilt.

Project Location

I have created a project repo on my github. You can download it from here. It contains two folder “starter” and “final”

  • “starter” — this project you can use to try out step by step on your own as we go further in this story 📖
  • “final” — this project is final project containing successfully running instrumented tests 🚀

Testing Scenario

In this article we will test a basic scenario. We want to test integration of ViewModel and Compose UI to make sure both are working together as expected. Compose UI is displaying a list of articles collected from ArticlesViewModel and the View Model is taking articles eventually from ArticlesRepository.

Let’s have a look at the code that we want to test.

Code Under Test

ArticlesScreen — Compose UI showing list of articles collecting from view model
ArticlesViewModel — taking list of articles from repository
MainActivity.kt — setting ArticlesScreen as its content
MainApplication.kt

This article will not explain about Hilt Annotations @AndroidEntryPoint, @HiltViewModel, @HiltAndroidApp you can read about them in detail from official documentation here .

Summarizing the steps we will do in writing instrumented tests, we will go each of them one by one

  • Prepare Test Data — we will provide fake/test data to run tests on.
  • Replace Binding/Module — Replacing production module with test module in order to use test/fake data
  • Setup HiltTestApplication — Writing Instrumented tests using Hilt requires to setup HiltTestApplication
  • Write Instrumented Tests — Defining rules and writing instrumented test, we will use Hilt annotation @HiltAndroidTestand some rules we will go in details below

Let’s start! 🚀

Dependencies

Following dependencies are required to write integration tests using Jetpack Compose and Hilt.

androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.2.0"
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'

Prepare Test Data

In our example (see code above) ArticlesScreen is collecting data from ArticlesViewModel and ArticlesViewModel is eventually taking data from ArticlesRepository so lets look at ArticlesRepository.kt

ArticlesRepository.kt is exposing a behaviour to provide a list of articles. In the production app we always take data from the backend so production code will be implementing this behaviour to provide data from the cloud. Eventually our production Hilt module will be binding production implementation using Hilt as below

But In our tests we do not want to use data from production and want to override this behaviour and provide FakeArticlesRepositoryImpl which will emit defined list of articles for our tests. see below

Now we have prepared our test data but Hilt is still having production module RepositoryModule.kt injected in it.

Replace Binding/Module

In order to use fake or mock instances of dependency for test you need to tell Hilt to not use binding which is used in production but rather replace it with test binding. To replace test binding we need to replace the production module which is using production binding with test module which is binding test dependency.

In our case we want to replace production implementation of a dependency from ArticlesRepositoryImpl.kt to FakeArticlesRepositoryImpl.kt. In order to do that we will replace RepositoryModule with RepositoryTestModule and bind fake instance of dependency inside RepositoryTestModul as below…

This will replace RepositoryModule with RepositoryTestModule for all of the Tests. @TestInstallIn annotation from Hilt does that by replacing RepositoryModule provided in its replaces parameter.

There are three ways to replace a binding in test, I wrote another story about it you can read it

Setup HiltTestApplication

Executing Instrumented tests using Hilt requires an Application object that supports Hilt, in order to achieve this Application class must be annotated with @HiltAndroidApp.

Hilt provides built in HiltTestApplication for that purpose which is already annotated with @HiltAndroidApp, we do not need to create a new Application class for test and annotate it but still if you want to have custom test application you can do that as well with annotation @CustomTestApplication, you can read about it from here But in this article we will not go into that details and use built in HiltTestApplication to run our tests on.

To use HitTestApplication in our instrumented tests we need to set up a custom test runner which will provide Hilt Test Application for your instrumented tests. This will work for all of the tests in our test project.

Now configure this test runner into the app level grade file, just replace the default test runner with this one, make sure to use the correct path. see below

Writing Instrumented Tests

To write Instrumented tests using Hilt we will annotate each test with @HiltAndroidTest

Lets see test code first and we will see in details afterwards

Following things to note..

  • Test is annotated with @HiltAndroidTest, we must annotate each test who uses Hilt with @HiltAndroidTest. This annotation is responsible for generating Hilt components for tests
  • Test has defined HiltAndroidRule. It is used to perform injection on your dependencies required for tests. So we must call hiltRule.inject() in setup method. hitRule.inject() will inject ArticlesRepository with FakeArticlesRepositoryImpl as we defined in our previous steps
  • Test has defined createAndroidComposeRule<MainActivity>. As we will be testing our compose UI defined in our activity so we need to use this rule to test UI.

There are different scenarios where you are required to create a separate activity for test, which I have explained in another story here

  • Rules are defined in an order using order = 0 or order = 1 for HiltAndroidRule and composeTestRule respectively, it will make sure HiltAndroidRule is created first because it creates all required Hilt components before using them for composeTestRule.
  • runTest is used in test method because we are working and testing on flow and it makes test efficient, you can read more about runTest here
  • In Test itself: first its checking articles list is visible by getting node via “testTagArticlesList” using api onNodeWithTag, and then collecting data from flow and check each of the data is displayed and using assertion api assertIsDisplayed

That's all!

Run your test and it should work 🚀.

You can download project from here, It contains final folder which has running test. 🚀

Do share your thoughts.. and let’s learn from each other. 👨‍🏫

Other stories in test series

Follow for more stories and 👏 if you like it :)

--

--

Saqib
Jetpack Composers

Senior Mobile Engineer (Android & iOS) , Berlin | Sharing my development experience