To Make Fragment Test Under Hilt Installed

Homan Huang
6 min readApr 7, 2021

--

If you have visited Hilt test guide, you shall know that Fragment container won’t work in Hilt because of @AndroidEntryPoint. I have tried on the Hilt-Fragment test method for a while. I found that is not hard to get around.

— === Menu === —

🌐 1. Download Files
🔨 2.
Gradle Setup
📁 3.
Folder Setup
………………
debug folder
………………java folder
………………package
📌 4.
Install the Files
………………
Insert HiltTestActivity.kt
………………
Insert debug AndroidManifest.xml
………………
Insert HiltExt.kt
🔬 5.
Test the Fragment

🌐 1. Download Files ……Menu

You can download the files from Mitchtabian or Google:

Or you can follow me to input those files by DIY. That’s the fun part of programming, isn’t it? Let’s skip step one.

🔨 2. Gradle Setup ……Menu

Gradle.Project:

buildscript {
repositories {
google()
mavenCentral()
}

ext.hilt_version = '2.33-beta'
ext.kotlin_version = "1.4.31"

dependencies {
classpath 'com.android.tools.build:gradle:7.0.0-alpha13'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.4"
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

Gradle.Module:

plugins {
id 'com.android.application'
id 'kotlin-android'
// dagger
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
id 'androidx.navigation.safeargs.kotlin'
}
android{...}dependencies {

implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

//region glide
// Glide
def glide_version = "4.11.0"
implementation "com.github.bumptech.glide:glide:$glide_version"
// Skip this if you don't want to use integration libraries or configure Glide.
kapt "com.github.bumptech.glide:compiler:$glide_version"
//endregion

//region navigation
// Navigation Components
def nav_version = "2.3.4"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
//endregion

//region activity and fragment
// Activity and Fragment
def activity_version = "1.2.1"
implementation "androidx.activity:activity-ktx:$activity_version"
def
fragment_version = "1.3.2"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
//endregion

//region dagger hilt + WorkManager
// android plugin
// id 'kotlin-kapt'
// id 'dagger.hilt.android.plugin'
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"

// For instrumentation tests
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version"

// For local unit tests
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version"

// For Workmanager
implementation "androidx.work:work-runtime-ktx:2.5.0"
implementation 'androidx.hilt:hilt-work:1.0.0-beta01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-beta01'
//endregion

//region room
def room_version = "2.3.0-rc01"

implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"

// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"

// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
//endregion

//region lifecycle
// Lifecycle
def lifecycle_version = "2.3.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

// optional - Test helpers for LiveData
def arch_version = "2.1.0"
// for InstantTaskExecutorRule()
testImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
//endregion

//region test
// junit
testImplementation "junit:junit:4.12"
androidTestImplementation "junit:junit:4.12"
// google.truth
testImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation "com.google.truth:truth:1.0.1"

// test core
def test_version = '1.4.0-alpha05'

debugImplementation "androidx.test:core-ktx:$test_version"
testImplementation "androidx.test:core-ktx:$test_version"
androidTestImplementation "androidx.test:core-ktx:$test_version"

testImplementation "androidx.test:runner:$test_version"
androidTestImplementation "androidx.test:runner:$test_version"

testImplementation "androidx.test:rules:$test_version"
androidTestImplementation "androidx.test:rules:$test_version"

// Assertions
def ext_junit_ver = '1.1.3-alpha05'
testImplementation "androidx.test.ext:junit:$ext_junit_ver"
androidTestImplementation "androidx.test.ext:junit:$ext_junit_ver"

testImplementation "androidx.test.ext:truth:$test_version"
androidTestImplementation "androidx.test.ext:truth:$test_version"


// Test: Hamcrest Assertion
testImplementation 'org.hamcrest:hamcrest:2.2'
// robolectric
testImplementation "org.robolectric:robolectric:4.4"

// mockito
def mockito_version = '3.8.0'
testImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.28.1'
androidTestImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
androidTestImplementation "org.mockito:mockito-inline:$mockito_version"

// Test: Espresso dependencies
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//endregion

//region retrofit + OkHttp + GSON
// Retrofit
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
// GSON
implementation "com.google.code.gson:gson:2.8.6"
// OkHttp
def OkHttp_version = '4.9.0'
implementation "com.squareup.okhttp3:okhttp:$OkHttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$OkHttp_version"
//endregion

//region kotlin coroutines
// Kotlin Coroutines
def coroutines_version = '1.4.3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
//endregion

}

📁 3. Folder Setup ……Menu

The fragment library is attached with debugImplementation.

Test Folder Structure

  • src / test — Gradle: testImplementation
  • src / androidTest — Gradle: androidTestImplementation
  • src / debug — Gradle: debugImplementation

Add a debug folder ……Menu

  • Switch to Project View
  • Down to src
  • Right-click → New → Directory
  • Input “debug” for the new directory

Ready:

Add a “java” folder ……Menu

  • Right-click debug folder →New →Folder →Java Folder

Add a package ……Menu

  • Copy the package name from the AndroidManifest.xml. For example,
  • Create a new package in the java folder:
  • Paste the package name:

That’s great! No typing for the package name. Too long, you’ll be easily input typoes.

📌 4. Install the Files ……Menu

debug/java/YOUR_PACKAGE ……Menu

Insert HiltTestActivity.kt

@AndroidEntryPoint
class HiltTestActivity : AppCompatActivity()

debug ……Menu

Insert debug AndroidManifest.xml

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

<application>
<activity
android:name="📦YOUR_PACKAGE.HiltTestActivity"
android:exported="false" />
</application>

</manifest>

📦YOUR_PACKAGE shall be one as same in the HiltTestActivity. Here is my example of 📦YOUR_PACKAGE:

If you put this file into the wrong place, you’ll see this error:

java.lang.RuntimeException: Unable to resolve activity for: Intent { act=... ./.HiltTestActivity (has extras) }

AndroidMenifest Fix: Open the java folder in Explorer

debug: Not Found!

java/

debug: Paste!

androidTest/java/YOUR_PACKAGE ……Menu

Insert HiltExt.kt.

  • Fix the red lines: It’s a little different I made. I add a constant string. The 👴old version has required a 🔑key from the fragment library but it has ✂️removed in the newest version.
const val THEME_EXTRAS_BUNDLE_KEY = "androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY"@ExperimentalCoroutinesApi
inline fun <reified T : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
fragmentFactory: FragmentFactory? = null,
crossinline action: T.() -> Unit = {}
) {
val mainActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra(THEME_EXTRAS_BUNDLE_KEY, themeResId)
ActivityScenario.launch<HiltTestActivity>(mainActivityIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
val
fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(T::class.java.classLoader),
T::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow()
(fragment as T).action()
}
}

🔬 5. Test the Fragment ……Menu

androidTest/java/YOUR_PACKAGE

You can create a empty test file to check your work:

This is the Live Template for the Hilt Test:

@ExperimentalCoroutinesApi
@SmallTest
@HiltAndroidTest
class $name$ {

@get:Rule
var hiltRule = HiltAndroidRule(this)

// single task rule
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

@Before
fun setup() {
hiltRule.inject()
}

$END$
}

Let’s give this shortcut a name as hilttest and put it in the Kotlin.

Test it in the new file.

Let’s inser a new testcase.

@Test
fun testLaunchFragment() {
launchFragmentInHiltContainer<SchoolScoresFragment> {
}
}

I have a SchoolScoresFragment to test. Let’s keep the body empty and 🏃run the test. Don’t forget to run the AVD first. Otherwise, the test will not be started.

Great! It’s working.……Menu

If you like this article, please clap!

Have a good Day!

--

--

Homan Huang

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.