To Make Fragment Test Under Hilt Installed
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:
- HiltExt.kt : Launch fragment under HiltTestActivity.
- HiltTestActivity.kt: An empty activity.
- AndroidManifest.xml: A debug manifest
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