Skip to content
All posts
March 15, 20263 min read

Testing With Hilt: Dependency Injection in Android Tests

Hilt makes dependency injection elegant in production code. In tests, it provides HiltAndroidRule and test modules that let you swap out real implementations for fakes. Here's how to use them correctly.

AndroidArchitectureTestingKotlin
Share:

Hilt solves dependency injection for production code. Test DI is a separate problem — and Hilt solves that too, with a different set of tools.

Here's how to wire up Hilt for testing so your test doubles (fakes, mocks) are injected correctly.


The Testing Setup

Add the Hilt testing dependency:

kotlin
// build.gradle.kts
androidTestImplementation("com.google.dagger:hilt-android-testing:2.51")
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.51")
testImplementation("com.google.dagger:hilt-android-testing:2.51")
kaptTest("com.google.dagger:hilt-android-compiler:2.51")

Every Hilt instrumentation test needs two things:

kotlin
@HiltAndroidTest
class TaskScreenTest {
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)
    
    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()
    
    @Inject
    lateinit var taskRepository: TaskRepository // Injected by Hilt
    
    @Before
    fun setup() {
        hiltRule.inject()
    }
}

code
HiltAndroidRule
manages the component lifecycle.
code
hiltRule.inject()
injects
code
@Inject
fields in the test class.


Replacing Real Implementations With Fakes

Create a test module that overrides your production module:

kotlin
// Production module
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    @Provides
    @Singleton
    fun provideTaskRepository(dao: TaskDao): TaskRepository =
        TaskRepositoryImpl(dao)
}

// Test module — replaces RepositoryModule in tests
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [RepositoryModule::class]
)
@Module
object FakeRepositoryModule {
    @Provides
    @Singleton
    fun provideTaskRepository(): TaskRepository = FakeTaskRepository()
}

The

code
@TestInstallIn
annotation makes Hilt use this module instead of
code
RepositoryModule
in all tests automatically.


Per-Test Replacement With @BindValue

Sometimes you want to inject specific instances per test, not globally:

kotlin
@HiltAndroidTest
class TaskViewModelTest {
    @get:Rule
    val hiltRule = HiltAndroidRule(this)
    
    @BindValue
    @JvmField
    val taskRepository: TaskRepository = FakeTaskRepository()
    
    // taskRepository is now what Hilt injects for TaskRepository
    
    @Test
    fun `loading tasks shows them in UI`() {
        (taskRepository as FakeTaskRepository).addTask(
            Task("1", "Buy milk", false)
        )
        // ... test code
    }
}

code
@BindValue
replaces the binding for that specific field in that specific test class.


Unit Tests Without Hilt

For unit tests (pure JVM, no Android framework), Hilt isn't needed. Inject dependencies manually:

kotlin
// No Hilt annotation needed
class TaskViewModelTest {
    private val fakeRepository = FakeTaskRepository()
    private val viewModel = TaskViewModel(repository = fakeRepository)
    
    @Test
    fun `empty state when no tasks`() = runTest {
        val state = viewModel.uiState.value
        assertTrue(state.tasks.isEmpty())
    }
}

This is faster and simpler. Reserve

code
@HiltAndroidTest
for instrumentation tests that need the full Android environment.


The HiltTestActivity

Hilt instrumentation tests require

code
HiltTestActivity
:

kotlin
// debug/AndroidManifest.xml
<activity
    android:name="dagger.hilt.android.testing.HiltTestActivity"
    android:exported="false" />

When testing Compose with Hilt:

kotlin
@get:Rule(order = 1)
val composeRule = createAndroidComposeRule<HiltTestActivity>()

Testing ViewModels With Hilt Injection

If your ViewModel is injected via

code
@HiltViewModel
:

kotlin
@HiltAndroidTest
class TaskViewModelHiltTest {
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)
    
    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()
    
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    
    private lateinit var viewModel: TaskViewModel
    
    @Before
    fun setup() {
        hiltRule.inject()
        viewModel = ViewModelProvider(composeRule.activity, viewModelFactory)[TaskViewModel::class.java]
    }
    
    @Test
    fun `viewmodel loaded with injected fake repository`() = runTest {
        // viewModel now uses FakeTaskRepository from FakeRepositoryModule
        val state = viewModel.uiState.value
        assertNotNull(state)
    }
}

Fake vs Mock: Which to Use With Hilt

Fakes (manual implementations of interfaces):

  • Best for Hilt tests — they're reusable across many tests
  • State is controllable and inspectable
  • No mock framework overhead
kotlin
class FakeTaskRepository : TaskRepository {
    private val tasks = mutableListOf<Task>()
    
    override suspend fun createTask(task: Task) { tasks.add(task) }
    override suspend fun getAllTasks() = tasks.toList()
    fun addTask(task: Task) = tasks.add(task) // Test helper
    fun clear() = tasks.clear()
}

Mocks (MockK or Mockito):

  • Good for verifying that a specific method was called
  • Per-test configuration
  • Verbosity increases for complex scenarios

For Hilt tests that run across many test classes, fakes are preferable. They're simpler to maintain than mock configurations.


Takeaways

  • code
    @HiltAndroidTest
    +
    code
    HiltAndroidRule
    is the standard setup for Hilt instrumentation tests
  • code
    @TestInstallIn
    replaces production modules globally across all tests in the test source set
  • code
    @BindValue
    replaces a specific binding for a single test class
  • Unit tests (pure JVM) don't need Hilt — inject manually for speed and simplicity
  • Fakes beat mocks for Hilt tests — they're reusable and simpler to configure
Share:
S

Sudarshan Chaudhari

AI Systems Builder / Product Engineer

Bangkok, Thailand

Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

MyFamilyTracker

Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this