3. 测试环境搭建:依赖注入(Hilt/手动)、测试模块配置、JUnit 与 Compose 测试库
State Hoisting 的 UI 测试需要稳定的测试环境,关键在于:依赖注入(DI) 让 ViewModel 和 Repository 在测试中可替换;测试模块 隔离非必要的真实组件;测试库 提供断言、Compose 节点查找和异步等待能力。本章覆盖从项目配置到测试框架集成的完整搭建流程。
3.1 依赖注入方案:Hilt vs. 手动注入
| 维度 | Hilt(推荐) | 手动注入(轻量) |
|---|---|---|
| 配置复杂度 | 中等(需添加 Gradle 插件 + Application 注解) | 低(直接构造对象) |
| 编译期校验 | 有(Dagger 编译时图校验) | 无(运行时错误) |
| 测试替换难度 | 通过 @Module / @TestInstallIn 轻松替换 | 需手动传递 Fake 对象 |
| 推荐场景 | 中型及以上项目 | 小型项目或快速原型 |
选型建议:本课程使用 Hilt +
@HiltViewModel 配合 @TestInstallIn 模块替换,无需在测试中手工创建 ViewModel 工厂。若项目已使用手动 DI,参考 3.1.2 节快速改造。
3.1.1 Hilt 集成步骤(API 级配置)
Step 1——项目级 build.gradle(或 settings.gradle.kts)添加 Hilt Gradle 插件:
// 项目级 build.gradle(Groovy)
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
}
}
Step 2——应用级 build.gradle 添加依赖:
// app/build.gradle
plugins {
id 'dagger.hilt.android.plugin'
}
android {
// 常规配置...
}
dependencies {
implementation 'com.google.dagger:hilt-android:2.51.1'
kapt 'com.google.dagger:hilt-compiler:2.51.1'
// 测试依赖(详见 3.3 节)
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
kaptAndroidTest 'com.google.dagger:hilt-compiler:2.51.1'
}
Step 3——创建 Application 类并添加 @HiltAndroidApp:
@HiltAndroidApp
class TimerApplication : Application()
Step 4——测试模块替换: 定义生产模块与测试模块,使用 @TestInstallIn 覆盖。
// 生产模块:注入真实 TimerRepository
@Module
@InstallIn(SingletonComponent::class)
object TimerModule {
@Provides
fun provideTimerRepository(): TimerRepository = RealTimerRepository()
}
// 测试模块:注入 FakeTimerRepository(位于 androidTest 源码集)
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [TimerModule::class]
)
@Module
object FakeTimerModule {
@Provides
fun provideFakeTimerRepository(): TimerRepository = FakeTimerRepository()
}
常见错误:测试模块必须放在
src/androidTest/java/ 目录下,否则插桩测试时不会生效。@TestInstallIn 的 replaces 数组需明确写出被替换的生产模块类名。
3.1.2 手动注入方案(备选)
若项目未使用 Hilt,可在测试中直接构造带假依赖的 ViewModel:
// 手动创建测试 ViewModel
class TimerViewModelTest {
private lateinit var viewModel: TimerViewModel
@Before
fun setUp() {
val fakeRepo = FakeTimerRepository()
viewModel = TimerViewModel(fakeRepo) // ViewModel 构造函数注入
}
@Test
fun timerStartsAtZero() {
assertEquals(0L, viewModel.uiState.value.elapsedTime)
}
}
3.2 测试模块配置(androidTest 源码集)
在 src/androidTest 目录下创建与主源码相同的包结构,放置测试基类、Fake 对象和 Hilt 测试 Runner。
// androidTest/java/com/example/timer/
├── test/
│ ├── TimerViewModelTest.kt
│ └── ui/
│ ├── TimerScreenTest.kt
│ └── helpers/
│ ├── TestHiltRunner.kt
│ └── FakeTimerRepository.kt
测试 Runner 配置:
// androidTest 中的自定义 Hilt 测试 Runner
class TimerTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
在 app/build.gradle 中指定自定义 Runner:
android {
defaultConfig {
testInstrumentationRunner "com.example.timer.test.TimerTestRunner"
// 也可以直接使用 Hilt 提供的 Runner:
// testInstrumentationRunner "com.google.dagger.hilt.android.testing.HiltTestRunner"
}
}
3.3 JUnit 与 Compose 测试库
需在 app/build.gradle 中添加以下依赖(确保版本与 Compose BOM 对齐):
dependencies {
// JUnit 5(亦可使用 JUnit 4,本课程以 JUnit 5 为例)
androidTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
androidTestImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
androidTestImplementation 'org.junit.vintage:junit-vintage-engine:5.10.2' // 兼容 JUnit 4
// Compose UI 测试库
androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
androidTestImplementation 'androidx.compose.ui:ui-test-manifest'
debugImplementation 'androidx.compose.ui:ui-test-manifest' // 用于 setContent 辅助
// Hilt 测试支持(已在 3.1.1 加入)
// androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
}
关键测试注解与类:
@HiltAndroidTest—— 标记测试类,开启 Hilt 组件注入。@get:Rule(order = 0)——HiltAndroidRule(this)初始化注入。createComposeRule()—— 用于启动 Compose content 的测试规则。
API 示例: 集成后的测试基类骨架
@HiltAndroidTest
class TimerScreenTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createComposeRule()
@Inject
lateinit var fakeRepository: TimerRepository
@Before
fun setUp() {
hiltRule.inject()
// 注入的 fakeRepository 已在 @TestInstallIn 中替换为 FakeTimerRepository
}
@Test
fun displayInitialState() {
composeTestRule.setContent {
TimerScreen(viewModel = hiltViewModel())
}
composeTestRule.onNodeWithText("00:00:00").assertExists()
}
}
3.4 测试环境搭建流程总览
3.5 常见错误与排查
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
测试启动时 IllegalStateException: Hilt components not found | 未使用 @HiltAndroidTest 启动 Runner | 在测试类上加注解,并确认 Runner 为 TimerTestRunner 或 HiltTestRunner |
FakeTimerRepository 未注入 | @TestInstallIn 的模块未被加载 | 检查模块是否位于 androidTest 源码集,并确认 replaces 类名与生产模块一致 |
Compose 测试中 onNodeWithText 找不到元素 | State Hoisting 未生效 / UI 渲染被异步遮挡 | 使用 composeTestRule.waitUntil(timeoutMs) 等待 recomposition 完成 |
| JUnit 5 测试方法不执行 | 缺少 junit-vintage-engine 或未配置 testInstrumentationRunnerArguments | 添加 androidTestImplementation 'org.junit.vintage:junit-vintage-engine:5.10.2' 并设置 Runner 为 AndroidJUnitRunner 结合 HiltTestRunner |
验证环境是否正确: 可先编写一个最简单的空测试(只需
assertEquals(1, 1))运行在模拟器上,确认 JUnit + Hilt 注入均无异常,再逐步添加 Compose UI 测试。