I'm working with navigation testing in Jetpack Compose and I have Hilt implemented for DI in my project. But when I run a UI test to check navigation I encounter this Exception
Given component holder class androidx.activity.ComponentActivity does not implement interface dagger.hilt.internal.GeneratedComponent or interface dagger.hilt.internal.GeneratedComponentManager
This is what I have done this so far:
My Gradle file looks like this
buildscript {
dependencies {
classpath("com.google.dagger:hilt-android-gradle-plugin:2.48")
}
}
plugins {
id ("com.android.application") version "8.1.1" apply false
id ("com.android.library") version "8.1.1" apply false
id ("org.jetbrains.kotlin.android") version "1.8.21" apply false
id ("com.google.dagger.hilt.android") version "2.48" apply false
id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}
tasks.register("clean",Delete::class){
delete(rootProject.buildDir)
}
plugins {
id ("com.android.application")
kotlin("android")
kotlin("kapt")
id ("dagger.hilt.android.plugin")
}
android {
namespace = "com.example.boundarys"
compileSdk = 34
defaultConfig {
applicationId = "com.example.boundarys"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "com.example.boundarys.HiltTestRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_18
targetCompatibility = JavaVersion.VERSION_18
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_18.toString()
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
coreLibraryDesugaring (libs.desugar.jdk.libs)
implementation(project(":KtorModule"))
implementation(project(":BxExtension"))
implementation (libs.core.ktx)
implementation (libs.lifecycle.runtime.ktx)
implementation (libs.activity.compose)
implementation (libs.ui)
implementation (libs.ui.tooling.preview)
implementation (libs.foundation)
implementation(libs.material3)
implementation("androidx.compose.material3:material3-window-size-class:1.2.0-alpha06")
implementation(libs.androidx.material)
// testing
testImplementation (libs.junit)
androidTestImplementation (libs.androidx.junit)
androidTestImplementation (libs.androidx.espresso.core)
androidTestImplementation (libs.androidx.ui.test.junit4)
debugImplementation (libs.androidx.ui.tooling)
debugImplementation (libs.androidx.ui.test.manifest)
testImplementation (libs.truth)
androidTestImplementation (libs.truth)
androidTestImplementation (libs.androidx.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.androidx.core.testing)
androidTestImplementation(libs.mockito.core)
androidTestImplementation(libs.mockito.kotlin)
androidTestImplementation(libs.androidx.navigation.testing)
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
// Compose dependencies
implementation (libs.androidx.lifecycle.viewmodel.compose)
implementation (libs.androidx.navigation.compose)
implementation ("androidx.compose.material:material-icons-extended")
// Dependency Injection
implementation(libs.hilt.android)
kapt(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.work)
kapt(libs.androidx.hilt.compiler)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.hilt.navigation.compose)
// For Robolectric tests.
testImplementation (libs.hilt.android.testing)
// ...with Kotlin.
kaptTest (libs.hilt.android.compiler)
// ...with Java.
testAnnotationProcessor (libs.hilt.android.compiler)
// For instrumented tests.
androidTestImplementation (libs.hilt.android.testing)
// ...with Kotlin.
kaptAndroidTest (libs.hilt.android.compiler)
// ...with Java.
androidTestAnnotationProcessor (libs.hilt.android.compiler)
// Lifecycle
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
// Room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.runtime.livedata)
kapt(libs.androidx.room.compiler)
annotationProcessor( libs.androidx.room.compiler)
// constraint layout
implementation (libs.androidx.constraintlayout.compose)
// Glide
implementation (libs.landscapist.glide)
// GSON
api(libs.gson)
}
I have this HiltTestRunner File and added to gradle you can see
testInstrumentationRunner = "com.example.boundarys.HiltTestRunner"
File look like this
class HiltTestRunner : AndroidJUnitRunner(){
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
My Activity is like this and define the application file in Manifest as well
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
init()
setContent {
BoundarysTheme {
Surface(modifier = Modifier.fillMaxSize()) {
BoundarysNavGraph()
}
}
}
}
}
@HiltAndroidApp
class BoundarysApp : Application(){
}
I have use nested graph for implementation
@Composable
fun BoundarysNavGraph(navController: NavHostController = rememberNavController()){
NavHost(
navController = navController,
startDestination =BoundarysNavRoutes.Splash.route){
navigation(
startDestination =SplashScreenRoutes.SPLASH.route ,
route=BoundarysNavRoutes.Splash.route,
){
splashNavGraph(navController)
}
}
}
fun NavGraphBuilder.splashNavGraph(navController: NavHostController){
horizontallyAnimatedComposableEnterOnly(SplashScreenRoutes.SPLASH.route){
val viewModel = hiltViewModel<SplashViewModel>()
SplashScreen(navController)
}
horizontallyAnimatedComposable(SplashScreenRoutes.LANDING.route){
val viewModel = hiltViewModel<LandingPageViewModel>()
LandingPageScreen(navController,viewModel.eventFlow){
viewModel.onEventUpdate(it)
}
}
horizontallyAnimatedComposable(SplashScreenRoutes.EXPLAINER.route){
val viewModel = hiltViewModel<ExplainerScreenViewModel>()
ExplainerScreen(navController,viewModel.eventFlow){
viewModel.onEventUpdate(it)
}
}
}
And My test file looks like this
@HiltAndroidTest
class SplashNavTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule
var hiltRule = HiltAndroidRule(this)
private lateinit var context: Context
private lateinit var navController: TestNavHostController
@Before
fun init(){
hiltRule.inject()
context = ApplicationProvider.getApplicationContext<Context>()
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
BoundarysNavGraph(navController = navController)
}
}
@Test
fun Verify_SplashIsVisible(){
val currentDestination = navController.currentBackStackEntry?.destination?.route
Truth.assertThat(currentDestination).isEqualTo(SplashScreenRoutes.SPLASH.route)
}
}
The first problem in your code lies in the fact that you have not specified the order for the composeTestRule and hiltRule. Besides that, in order to be able to access the corresponding activity, the composeTestRule should be created using createAndroidComposeRule. So your code should be changed as below:
@get:Rule(order = 0) //👈
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) //👈
val composeTestRule = createAndroidComposeRule<MainActivity>() //👈
Now, when setting the content, remember to specify the activity:
composeTestRule.activity.setContent { ... }
// 👆
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With