Writing Instrumented Tests Using Hilt And Jetpack Compose
In this story we will see how to write Instrumented Tests using Jetpack Compose
and Hilt
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
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 setupHiltTestApplication
- Write Instrumented Tests — Defining rules and writing instrumented test, we will use Hilt annotation
@HiltAndroidTest
and 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 sureHiltAndroidRule
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 aboutrunTest
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 apiassertIsDisplayed
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
- Writing UI Tests for Jetpack Compose Navigation
- 3 Ways To Replace a Binding in Tests using Hilt
- Hilt Container Activity for Test