Sitemap

Set Up Android Jetpack Compose Instrumentation Test with Hilt

A step-by-step guide to set up the Android Jetpack Compose UI test with Hilt dependency injection

Press enter or click to view image in full size

Testing is a crucial part of Android development, and based on my experience, setting up instrumentation tests can be challenging — especially when using Hilt for dependency injection.

In this article, I will guide you through the setup process, ensuring your Compose UI tests run smoothly with Hilt. I will be using my app, “TrendNow”, for the sample. You can see the series here TrendNow: Build an Android News App with Jetpack Compose.

1. Set up the test dependencies

I’m using the Gradle version catalog to manage the app dependencies, you can read more about it here Migrate your build to version catalogs.

# inside libs.versions.toml
[versions]
composeBom = "2025.03.00"
espressoCore = "3.6.1"
junit = "4.13.2"
hilt = "2.55"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
# junit4 version managed by compose bom
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
hilt-test = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
hilt-ksp = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }

The dependencies above are required, add them to the app build.gradle.kts.

...

dependencies {
...

androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
androidTestImplementation(libs.hilt.test)
kspAndroidTest(libs.hilt.ksp)
}

Once done, “Sync Project with Gradle Files” to install the dependencies.

2. Configure Hilt for instrumentation test

Next, we need to configure the Test Runner and Activity for Hilt.

Set up the Hilt test runner

Create a custom runner inside the androidTest directory.

import dagger.hilt.android.testing.HiltTestApplication

class HiltTestRunner : AndroidJUnitRunner() {

override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(
cl,
HiltTestApplication::class.java.name,
context
)
}
}
  • Use HiltTestApplication class that comes from this dependency hilt-test = { group = “com.google.dagger”, name = “hilt-android-testing” }
Press enter or click to view image in full size
Custom runner file for instrumentation test with Hilt

Then register the runner to testInstrumentationRunner inside the app build.gradle.kts file.

android {
...

defaultConfig {
applicationId = "com.trend.now"
minSdk = 24
targetSdk = 35
...

// make sure to set the package name and class name correctly
testInstrumentationRunner = "com.trend.now.HiltTestRunner"
}
...
}

Set up the Hilt test activity

Creating a Hilt test activity is required to make sure the dependency injection to Android components (a class that has annotation @AndroidEntryPoint) is working.

Get Dani Mahardhika’s stories in your inbox

Join Medium for free to get updates from this writer.

Why not just use the actual Activity? Because the actual Activity usually contains complex navigation which makes testing a specific screen harder. Unless the test involves screen navigation, it’s recommended to create a dedicated activity for the test.

abstract class BaseActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
}

...
}

@AndroidEntryPoint
class MainActivity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set the activity content and navigation
setContent {
TrendNowTheme {
AppNavigation()
}
}
}
}
  • Create an abstract class BaseActivity and move the original activity behavior except for the app content, navigation, and @AndroidEntryPoint annotation.
  • Create another Activity that extends the BaseActivity with @AndroidEntryPoint annotation that sets the app content and navigation.

Then create the Hilt test activity inside the debug directory.

@AndroidEntryPoint
class HiltTestActivity : BaseActivity()
  • Extend the BaseActivity so that the Hilt test activity inherits the original activity behavior.

Notes:

  • Creating a base activity that contains the original activity behavior is optional, you can use an empty activity if it’s enough.
  • Move the required behavior or code only to the base activity instead of moving all of them. Why? To exclude unrelated behavior with the UI test requirements.

Then create AndroidManifest.xml file inside the debug directory and register the Hilt test activity.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">

<application tools:ignore="MissingApplicationIcon">
<activity
android:name="com.trend.now.HiltTestActivity"
android:exported="false">
</activity>
</application>
</manifest>
  • No need to add android.intent.action.MAIN and android.intent.category.LAUNCHER.
Press enter or click to view image in full size
Hilt test activity for instrumentation test

3. Write a UI test in a Hilt-enabled Android Compose app

After the steps above are done, then we can write our UI test.

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class NewsScreenTest {

@get:Rule
var hiltRule = HiltAndroidRule(this)

@get:Rule
val composeTestRule = createAndroidComposeRule<HiltTestActivity>()

@Test
fun shouldShowAppTitleOnTheTopAppBar() {
// when
composeTestRule.setContent {
// set the screen that you want to test here
TrendNowTheme {
NewsScreen(navController = rememberNavController())
}
}

// then
composeTestRule.onNodeWithText("TrendNow").assertIsDisplayed()
}
}
  • Make sure to add @HiltAndroidTest annotation to the test class to enable Hilt dependency injection within the test environment.
  • HiltAndroidRule is required to initialize Hilt and ensure the Hilt dependencies are injected before the test starts.
  • onNodeWithText() is used to find a component with specific text.
  • assertIsDisplayed() is used to make sure the component found (above) is visible to the user.
Press enter or click to view image in full size
UI test result

Now that you’ve set up Hilt for Jetpack Compose tests, you have a solid foundation for writing reliable UI tests. See the official documentation for more info Hilt testing. Happy testing! 🎯

Troubleshooting

How to use a custom application for the test?

Using the default HiltTestApplication mostly is enough, but if it still does not satisfy your needs, we can create a custom Hilt test application.

Similar to the Hilt test activity, we can create an abstract class to include the original app behavior.

abstract class BaseApplication : Application() {

override fun onCreate() {
super.onCreate()
...
}

...
}

@HiltAndroidApp
class NewsApplication : BaseApplication()
  • Create an abstract class BaseApplication and move the original application behavior except for the @HiltAndroidApp annotation.
  • Create another Application that extends the BaseApplication with @HiltAndroidApp annotation.

Then we can create the custom Hilt test application inside the androidTest directory.

import dagger.hilt.android.testing.CustomTestApplication

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication
  • You can use a different name for the interface name, I’m using HiltTestApplication here.
  • Make sure to pass the abstract class BaseApplication created above to the @CustomTestApplication.

Then rebuild the project using Build → Rebuild Project or using this gradle command,

./gradlew clean assembleDebugAndroidTest

After that the HiltTestApplication_Application class will be generated inside the build directory like the image below,

Press enter or click to view image in full size
Generated custom Hilt application class
  • The generated file name format should be InterfaceName_Application.

Then register the generated class to the HiltTestRunner class.

class HiltTestRunner : AndroidJUnitRunner() {

override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(
cl,
// register here
HiltTestApplication_Application::class.java.name,
context
)
}
}
  • Replace the default HiltTestApplication with the custom Application.

How to solve an error “Unable to resolve activity for: Intent…”?

Unable to resolve activity for: Intent { 
act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
cmp=com.your.package.test/com.your.package.HiltTestActivity }

If you get any error related to the Hilt test activity or Hit test application, make sure,

  1. Both of HiltTestActivity and AndroidManifest.xml are placed inside the debug directory, not androidTest directory.
  2. The Hilt test application is placed inside the androidTest directory, no need to declare it in the AndroidManifest.xml.

Written by Dani Mahardhika

Software Engineer from Indonesia 🇮🇩 with 9+ years of experience | Vibe Coding Android (Compose) and iOS (SwiftUI)