我正在 Jetpack Compose 中进行导航测试,并且在我的项目中为 DI 实现了 Hilt。但是当我运行 UI 测试来检查导航时,我遇到了这个异常
给定的组件持有者类 androidx.activity.ComponentActivity 未实现接口 dagger.hilt.internal.GenerateComponent 或接口 dagger.hilt.internal.GenerateComponentManager
这就是我到目前为止所做的:
我的 Gradle 文件看起来像这样
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)
}
我有这个 HiltTestRunner 文件并添加到 gradle 你可以看到
testInstrumentationRunner = "com.example.boundarys.HiltTestRunner"
文件看起来像这样
class HiltTestRunner : AndroidJUnitRunner(){
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
我的Activity是这样的,也在Manifest中定义了应用程序文件
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
init()
setContent {
BoundarysTheme {
Surface(modifier = Modifier.fillMaxSize()) {
BoundarysNavGraph()
}
}
}
}
}
@HiltAndroidApp
class BoundarysApp : Application(){
}
我使用嵌套图来实现
@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)
}
}
}
我的测试文件看起来像这样
@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)
}
}
代码中的第一个问题在于您not指定了
composeTestRule
和hiltRule
的顺序。除此之外,为了能够访问相应的活动,应使用 composeTestRule
创建 createAndroidComposeRule
。所以你的代码应该更改如下:
@get:Rule(order = 0) //👈
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) //👈
val composeTestRule = createAndroidComposeRule<MainActivity>() //👈
现在,设置内容时,记得指定活动:
composeTestRule.activity.setContent { ... }
// 👆