关于 configuring-junit4-on-android
面向 Android Kotlin 测试开发者的 JUnit4 配置指南,整理 AndroidJUnit4、InstrumentationRegistry、Context、测试规模标注、Truth 断言及主调度器规则的用法。
主要功能
- 01选择规范的 AndroidJUnit4 Runner
- 02使用规范的 InstrumentationRegistry 与 ApplicationProvider
- 03按规模标注测试并筛选运行
- 04通过 Truth 断言 Android 对象
- 05为 Dispatchers.Main 配置 MainDispatcherRule
适用场景
- 01修复 AndroidJUnit4 或 InstrumentationRegistry 导入问题
- 02配置依赖仪器测试参数的测试
- 03按 small、medium、large 筛选测试
- 04测试使用 Dispatchers.Main 的 ViewModel 或协程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill configuring-junit4-on-android安装后在 Claude Code 中输入/configuring-junit4-on-android确认已加载。
详细介绍
在 Android 上配置 JUnit4——正确的 Runner、Registry 和依赖矩阵
几乎所有“测试套件配置错误”的问题,都可归结为三个根本原因之一:导入了错误的 AndroidJUnit4、导入了错误的 InstrumentationRegistry,或者缺少 size 注解 / dispatcher 配置。本技能整理了从 androidx.test:core 1.7.0 起的精确 Gradle 依赖矩阵、规范的 FQCN,以及 androidx 自己在 testutils-ktx 中提供的最小 MainDispatcherRule 模板。
何时使用此技能
- 用户报告
Cannot resolve symbol AndroidJUnit4、AndroidJUnit4 is deprecated,或询问“应该导入哪个AndroidJUnit4”。 - 用户在单元测试中调用
InstrumentationRegistry.getInstrumentation()时遇到IllegalStateException: No instrumentation registered,或询问为什么InstrumentationRegistry被划掉了。 - 用户需要 Truth subject(
IntentSubject、BundleSubject、LocationSubject、ParcelableSubject),但不知道它们由哪个 artifact 提供。 - 用户希望使用
am instrument -e size small筛选测试,并询问如何配置@SmallTest/@MediumTest/@LargeTest。 - 用户正在编写
ViewModel/ 协程测试,需要规范的MainDispatcherRule模板。
何时不使用此技能
- 用户正在配置 Mockito mock——请使用
../../mocking/mocking-with-mockito/SKILL.md。 - 用户正在配置 MockK mock——请使用
../../mocking/mocking-with-mockk/SKILL.md。 - 用户正在配置
runTest { … }或Turbine——请使用../../coroutines/testing-coroutines-with-runtest/SKILL.md或../../coroutines/testing-flows-with-turbine/SKILL.md。 - 用户正在专门配置 Robolectric(SDK 矩阵、资源、
@Config)——请使用../../robolectric/using-robolectric-correctly/SKILL.md。 - 用户正在设备上使用
am instrument运行测试——请使用../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。 - 用户正在选择 fake 还是 mock——请使用
../../../fundamentals/doubles/picking-test-doubles/SKILL.md。
前置条件
- 已应用 Android Gradle Plugin(
com.android.application或com.android.library)。 - 磁盘上有
src/test/source set(Gradle 会按需创建;如果不存在,请运行mkdir -p src/test/kotlin)。 - Kotlin 模块(本文假设使用 Kotlin;相同的 FQCN 也适用于 Java)。
- 对于协程相关测试,classpath 中已包含
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.x。
工作流
- 1. 固定规范的 Gradle 依赖矩阵。 以下是本技能采用的已发布稳定版本(已根据
~/.gradle/caches/modules-2/files-2.1/androidx.test/...验证)。请直接固定这些版本,不要使用latest.release:
dependencies {
// Core JUnit4
testImplementation("junit:junit:4.13.2")
// androidx.test foundations (work in src/test/ via Robolectric and in src/androidTest/)
testImplementation("androidx.test:core:1.7.0")
testImplementation("androidx.test:core-ktx:1.6.1")
testImplementation("androidx.test:runner:1.7.0")
testImplementation("androidx.test:rules:1.7.0")
testImplementation("androidx.test.ext:junit:1.3.0")
testImplementation("androidx.test.ext:junit-ktx:1.3.0")
testImplementation("androidx.test.ext:truth:1.7.0")
testImplementation("com.google.truth:truth:1.4.4")
// Coroutines test (for MainDispatcherRule / runTest)
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1")
}
对于 src/androidTest/ 中的仪器化测试,请在 androidTestImplementation 上配置相同的 artifacts。这些 artifacts 兼容双 classpath。
- 2. 使用正确的
@RunWith注解标记测试类。 classpath 上有两个名为AndroidJUnit4的类。已弃用的类是androidx.test.runner.AndroidJUnit4(文件runner-1.7.0/androidx/test/runner/AndroidJUnit4.javaL41-44——标记为@Deprecated)。规范的类是androidx.test.ext:junit:1.3.0artifact 中的androidx.test.ext.junit.runners.AndroidJUnit4(文件junit-1.3.0/androidx/test/ext/junit/runners/AndroidJUnit4.javaL49)。它在 JVM 上会委托给 Robolectric(当 classpath 中存在org.robolectric.RobolectricTestRunner时),在设备上则会委托给androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner。
import androidx.test.ext.junit.runners.AndroidJUnit4 // canonical
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyTest { /* ... */ }
- 3. 使用规范的
InstrumentationRegistry。androidx.test.InstrumentationRegistry(没有.platform.app.)从:monitor1.x 起已标记为@Deprecated——其所有成员都通过@InlineMe指向替代项。规范的类是androidx.test.platform.app.InstrumentationRegistry(文件monitor-1.8.0/androidx/test/platform/app/InstrumentationRegistry.javaL30-85)。
import androidx.test.platform.app.InstrumentationRegistry
val instrumentation = InstrumentationRegistry.getInstrumentation()
val args: Bundle = InstrumentationRegistry.getArguments() // copy of -e key/value pairs
getInstrumentation() 若在 instrumentation 宿主之外调用,或在 Robolectric 初始化其 shadow 之前调用,会抛出 IllegalStateException("No instrumentation registered! Must run under a registering instrumentation.")。Robolectric 会在测试运行前将一个 Instrumentation 实例注册到 registry 中,因此在 src/test/ 中启用 @RunWith(AndroidJUnit4::class) 时,此方法可以正常工作。
- 4. 通过
ApplicationProvider获取 Application Context,而非通过 registry。ApplicationProvider.getApplicationContext()是获取 Application Context 的唯一入口(文件core-1.7.0/androidx/test/core/app/ApplicationProvider.javaL29-43)。其内部返回getInstrumentation().getTargetContext().getApplicationContext()——Context 相同,调用处更易读,而且未检查的泛型允许调用方将其转换为自己的Application子类。
import androidx.test.core.app.ApplicationProvider
val ctx: Context = ApplicationProvider.getApplicationContext()
val app: MyApp = ApplicationProvider.getApplicationContext() // unchecked cast
- 5. 当测试套件规模大到需要筛选时,为每个测试添加大小注解。 大小分为三种:
@SmallTest(<200ms,不使用 Android 桩)、@MediumTest(<1000ms,通过 Robolectric 使用 Android 框架)、@LargeTest(>1000ms,经过 instrumentation)。这些注解位于androidx.test.filters.*(文件runner-1.7.0/androidx/test/filters/SmallTest.javaL44-46)。Runner 的RunnerArgs.SIZE(runner-1.7.0/androidx/test/internal/runner/RunnerArgs.javaL58)会将-e size <value>直接映射到这些注解。
import androidx.test.filters.SmallTest
import androidx.test.filters.MediumTest
@SmallTest
class FastValidatorTest { /* ... */ }
@MediumTest
class RoomDaoTest { /* ... */ }
相应的 am instrument 调用为 adb shell am instrument -w -r -e size small <pkg>/androidx.test.runner.AndroidJUnitRunner。设备端的对应内容请参阅 ../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。
- 6. 对 Android 领域的断言,优先使用
androidx.test.ext:truth中的 Truth subject。 Truth 随androidx.test.ext:truth:1.7.0一同发布,增加了面向 Android 的Subject类型。每种类型都提供static assertThat(T actual)快捷方法和static Subject.Factory<T, S> name()工厂方法:
| Subject | 完全限定类名包 | 断言对象 |
|---|---|---|
IntentSubject | androidx.test.ext.truth.content | android.content.Intent(action、data、component、extras、categories、flags) |
BundleSubject / BaseBundleSubject / PersistableBundleSubject | androidx.test.ext.truth.os | Bundle / BaseBundle / PersistableBundle 的键、类型和值 |
ParcelableSubject | androidx.test.ext.truth.os | Parcelable(通过 Parcel 往返传递,以验证写入器/读取器) |
LocationSubject | androidx.test.ext.truth.location | android.location.Location |
NotificationSubject / NotificationActionSubject / PendingIntentSubject | androidx.test.ext.truth.app | Notification 系列 |
MotionEventSubject / PointerCoordsSubject / PointerPropertiesSubject | androidx.test.ext.truth.view | MotionEvent 系列 |
SparseBooleanArraySubject | androidx.test.ext.truth.util | SparseBooleanArray |
惯用的导入和调用方式:
import androidx.test.ext.truth.content.IntentSubject.assertThat
import androidx.test.ext.truth.os.BundleSubject
@Test fun intent_routes_to_settings() {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("settings://wifi"))
assertThat(intent).hasAction(Intent.ACTION_VIEW)
assertThat(intent).hasData(Uri.parse("settings://wifi"))
}
- 7. 对任何使用
Dispatchers.Main的测试,安装MainDispatcherRule。 Compose、viewModelScope和LiveData默认都使用Dispatchers.Main,除非替换它,否则在 JVM 上不可用。androidx 的规范模式位于testutils/testutils-ktx/src/jvmMain/kotlin/androidx/testutils/MainDispatcherRule.jvm.kt——请原样使用:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description
@OptIn(ExperimentalCoroutinesApi::class)
class MainDispatcherRule(
val testDispatcher: TestDispatcher = StandardTestDispatcher(),
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
Dispatchers.resetMain()
}
}
应用于测试类:
@RunWith(AndroidJUnit4::class)
class MyViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@Test fun loadsUsers() = runTest {
val vm = MyViewModel(repo = FakeUserRepository())
vm.load()
advanceUntilIdle()
assertThat(vm.users.value).isNotEmpty()
}
}
首选 StandardTestDispatcher——它会将续体加入队列,并且只在调用 runCurrent()/advanceUntilIdle() 时推进,这与 kotlinx.coroutines.test.runTest 的语义一致。UnconfinedTestDispatcher 会立即分发,仅适用于执行顺序无关紧要的情况。
- 8. 确认测试类确实会运行。 执行一次测试套件:
./gradlew :<module>:testDebugUnitTest --tests com.example.MyTest。Gradle 输出会显示运行器选择情况——org.junit.runner.RunWith: AndroidJUnit4表示已绑定规范运行器;顶部附近的Test process exited with status code表示 Robolectric 已完成配置。
模式
模式:错误与正确——选择 AndroidJUnit4 运行器
// WRONG
import org.junit.runners.JUnit4
import org.junit.runner.RunWith
@RunWith(JUnit4::class)
class MyTest { /* uses InstrumentationRegistry, ApplicationProvider, ... */ }
// WRONG because: stock JUnit4 does not register the Android `Instrumentation` instance
// into InstrumentationRegistry. ApplicationProvider.getApplicationContext() throws
// IllegalStateException("No instrumentation registered!"). Robolectric is also never
// activated even when on the classpath, so all framework calls hit unmocked stubs.
// RIGHT
import androidx.test.ext.junit.runners.AndroidJUnit4 // ext.junit.runners, NOT runner.AndroidJUnit4
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyTest { /* ... */ }
已弃用的别名 androidx.test.runner.AndroidJUnit4 仍可编译,但会产生未抑制的弃用警告。新代码必须从 androidx.test.ext.junit.runners 导入。
模式:错误与正确——导入 InstrumentationRegistry
// WRONG
import androidx.test.InstrumentationRegistry // @Deprecated since :monitor
val ctx = InstrumentationRegistry.getContext() // also deprecated — not the Application context
// WRONG because: every member of androidx.test.InstrumentationRegistry is @Deprecated
// and @InlineMe'd to its replacement. getContext() returns the instrumentation package
// context, NOT the Application context — common source of "resource not found" failures.
// RIGHT
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.core.app.ApplicationProvider
val ctx: Context = ApplicationProvider.getApplicationContext() // application context
val args: Bundle = InstrumentationRegistry.getArguments() // -e key/value pairs
val instr = InstrumentationRegistry.getInstrumentation()
模式:错误与正确——在单元测试中访问 Compose Dispatchers.Main
// WRONG
class MyViewModelTest {
@Test fun loadsUsers() = runBlocking {
val vm = MyViewModel(repo = FakeUserRepository())
vm.load()
// Throws IllegalStateException: "Module with the Main dispatcher is missing"
}
}
// WRONG because: kotlinx-coroutines-android (the Main dispatcher impl for Android) is
// NOT on the unit-test classpath. Without Dispatchers.setMain(...), any viewModelScope or
// LaunchedEffect that touches the Main dispatcher fails immediately.
// RIGHT
class MyViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@Test fun loadsUsers() = runTest {
val vm = MyViewModel(repo = FakeUserRepository())
vm.load()
advanceUntilIdle()
assertThat(vm.users.value).hasSize(2)
}
}
模式:从命令行按大小筛选
@SmallTest / @MediumTest / @LargeTest 注解会直接绑定到 RunnerArgs.SIZE(runner-1.7.0/androidx/test/internal/runner/RunnerArgs.java L58)。对于插桩测试:
adb shell am instrument -w -r -e size small \
com.example.test/androidx.test.runner.AndroidJUnitRunner
对于运行在 JVM 上的 Robolectric 测试,Gradle 属性筛选是标准方式:
./gradlew :app:testDebugUnitTest --tests "com.example.*" \
-PandroidTestSize=small
结合 -e annotation com.example.SmokeTest(即 RunnerArgs.ANNOTATION 参数,L60),即可组合筛选条件。
强制规则
- 必须从
androidx.test.ext.junit.runners导入AndroidJUnit4。不得导入androidx.test.runner.AndroidJUnit4——它已标记为@Deprecated。 - 必须从
androidx.test.platform.app导入InstrumentationRegistry。不得导入androidx.test.InstrumentationRegistry——它已标记为@Deprecated。 - 必须使用
ApplicationProvider.getApplicationContext()获取 Application 上下文。不得直接调用InstrumentationRegistry.getInstrumentation().getTargetContext()——ApplicationProvider是规范入口,可处理未经检查的类型转换。 - 必须在任何会触及
viewModelScope、LaunchedEffect或任何Dispatchers.Main使用者的测试之前安装MainDispatcherRule(或等效的Dispatchers.setMain/resetMain配置)。否则,测试会因IllegalStateException: Module with the Main dispatcher is missing而崩溃。 - 必须在
MainDispatcherRule中优先使用StandardTestDispatcher,而不是UnconfinedTestDispatcher。StandardTestDispatcher与runTest语义一致;立即调度可能导致执行顺序出错。 - 当测试套件按大小筛选时,必须为每个测试类或方法恰好添加一个大小注解(
@SmallTest/@MediumTest/@LargeTest)。在同一目标上使用多个大小注解会导致运行器行为未定义。 - 必须单独指定 Robolectric 版本——它不包含在 androidx-test BOM 中。使用与模块
compileSdk匹配的org.robolectric:robolectric:4.x。 - 新代码中不得使用
runBlockingTest { }——自kotlinx-coroutines-test1.7 起,它已标记为@Deprecated(level = DeprecationLevel.ERROR)。请改用runTest { }。 - 不得使用
ActivityTestRule<A>——它已标记为@Deprecated。请改用androidx.test.ext.junit.rules.ActivityScenarioRule<A>(junit-1.3.0/androidx/test/ext/junit/rules/ActivityScenarioRule.javaL56)。有关插桩测试版本,请参阅../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。 - **首选:**在模块的
android { … }代码块中声明testOptions.unitTests.isIncludeAndroidResources = true。Robolectric 需要合并后的资源,才能填充任何布局或读取R.string.*。
验证
-
./gradlew :<module>:testDebugUnitTest可成功编译,并为新测试套件报告Tests run: N, Failures: 0。 -
grep -r "import androidx.test.runner.AndroidJUnit4" src/test src/androidTest无匹配结果(仅允许使用androidx.test.ext.junit.runners.AndroidJUnit4)。 -
grep -r "import androidx.test.InstrumentationRegistry" src/test src/androidTest无匹配结果。 - 任何使用
viewModelScope/Dispatchers.Main的测试都声明@get:Rule val mainDispatcherRule = MainDispatcherRule(),并使用runTest { … }。 - 如果测试套件需要筛选,至少有一个测试类使用
@SmallTest、@MediumTest或@LargeTest注解。 -
./gradlew :<module>:testDebugUnitTest --info的输出在运行器堆栈中显示org.robolectric.RobolectricTestRunner(主机)或AndroidJUnit4ClassRunner(设备),以确认选择了AndroidJUnit4代理。
参考资料
- AndroidX Test 版本发布记录:https://developer.android.com/jetpack/androidx/releases/test
- 本地测试指南:https://developer.android.com/training/testing/local-tests
- 插桩测试指南:https://developer.android.com/training/testing/instrumented-tests
- AndroidJUnitRunner 参考:https://developer.android.com/training/testing/junit-runner
- Truth subject 参考(上游代码树):https://github.com/android/android-test/tree/main/ext/truth/java/androidx/test/ext/truth
runner-1.7.0/androidx/test/runner/AndroidJUnit4.java(L41-44)——@Deprecated别名。junit-1.3.0/androidx/test/ext/junit/runners/AndroidJUnit4.java(L49-70)——规范运行器,可选择 Robolectric 或 Android 代理。monitor-1.8.0/androidx/test/platform/app/InstrumentationRegistry.java(L30-85)——规范注册表;提供getInstrumentation、getArguments、registerInstance。monitor-1.8.0/androidx/test/InstrumentationRegistry.java(L34)——@Deprecated旧版注册表。core-1.7.0/androidx/test/core/app/ApplicationProvider.java(L29-43)——单方法门面,返回 Application 上下文。runner-1.7.0/androidx/test/filters/SmallTest.java(L44-46)、MediumTest.java、LargeTest.java——测试规模注解定义。runner-1.7.0/androidx/test/internal/runner/RunnerArgs.java(L54-99)——运行器接受的所有-e <key> <value>参数,包括size、annotation、class、package、numShards/shardIndex。testutils/testutils-ktx/src/jvmMain/kotlin/androidx/testutils/MainDispatcherRule.jvm.kt——规范的 androidxMainDispatcherRule源码,与此处提供的模式相同。- 跨测试集:
../../../kotlin/kotlin-test/writing-tests-with-kotlin-test/SKILL.md——这些 JUnit4 测试中使用的、与框架无关的kotlin.test断言(assertEquals、assertFailsWith、@BeforeTest);也是commonTest中唯一可用的断言 API。