configuring-junit4-on-android について
Android Kotlin のテスト作成者向けの JUnit4 設定ガイドです。標準の AndroidJUnit4 ランナーとレジストリ、アプリケーションコンテキスト、サイズ注釈、Android 用 Truth 断言、メインディスパッチャールールを扱います。
主な機能
- 01標準の AndroidJUnit4 ランナーを選択
- 02InstrumentationRegistry と ApplicationProvider の使用
- 03サイズ注釈によるテスト分類と絞り込み
- 04Truth による Android オブジェクトの検証
- 05Dispatchers.Main 用 MainDispatcherRule の設定
活用シーン
- 01AndroidJUnit4 や InstrumentationRegistry のインポート問題を修正
- 02Instrumentation の引数を使うテストを設定
- 03small、medium、large のサイズでテストを絞り込み
- 04Dispatchers.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、依存関係マトリクス
「テストスイートの設定が正しくありません」という報告の原因は、ほぼすべて次の3つのいずれかです。誤った AndroidJUnit4 をインポートしている、誤った InstrumentationRegistry をインポートしている、またはサイズアノテーション/ディスパッチャーの設定が不足している、のいずれかです。このスキルでは、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 のサブジェクト(
IntentSubject、BundleSubject、LocationSubject、ParcelableSubject)を使いたいものの、どのアーティファクトに含まれているかわからない場合。 - ユーザーが
am instrument -e size smallでテストを絞り込みたいと考え、@SmallTest/@MediumTest/@LargeTestをどう設定するか尋ねた場合。 - ユーザーが
ViewModel/コルーチンのテストを書いていて、正式なMainDispatcherRuleテンプレートが必要な場合。
このスキルを使用しない場面
- ユーザーが Mockito モックを設定している場合は、
../../mocking/mocking-with-mockito/SKILL.mdを使用してください。 - ユーザーが MockK モックを設定している場合は、
../../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を使用してください。 - ユーザーがフェイクとモックのどちらを使うか選んでいる場合は、
../../../fundamentals/doubles/picking-test-doubles/SKILL.mdを使用してください。
前提条件
- Android Gradle Plugin が適用されていること(
com.android.applicationまたはcom.android.library)。 - ディスク上に
src/test/ソースセットがあること(Gradle は必要になるまで作成しません。見つからない場合はmkdir -p src/test/kotlinを実行してください)。 - Kotlin モジュールであること(この説明は Kotlin を前提としていますが、同じ FQCN は Java でも使用できます)。
- コルーチン関連のテストでは、
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 にも追加してください。これらのアーティファクトは、どちらのクラスパスでも使用できます。
- 2. テストクラスに正しい
@RunWithを指定します。 クラスパスには、名前がAndroidJUnit4のクラスが2つあります。非推奨のクラスはandroidx.test.runner.AndroidJUnit4です(ファイルrunner-1.7.0/androidx/test/runner/AndroidJUnit4.javaの L41-44 に@Deprecatedがあります)。正式なクラスは、androidx.test.ext:junit:1.3.0アーティファクトに含まれるandroidx.test.ext.junit.runners.AndroidJUnit4です(ファイルjunit-1.3.0/androidx/test/ext/junit/runners/AndroidJUnit4.javaの L49)。JVM 上ではクラスパスにorg.robolectric.RobolectricTestRunnerがある場合に Robolectric に委譲し、デバイス上では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.javaの L30-85)。
import androidx.test.platform.app.InstrumentationRegistry
val instrumentation = InstrumentationRegistry.getInstrumentation()
val args: Bundle = InstrumentationRegistry.getArguments() // copy of -e key/value pairs
getInstrumentation() を計測環境ホストの外で、または Robolectric がシャドウを初期化する前に呼び出すと、IllegalStateException("No instrumentation registered! Must run under a registering instrumentation.") がスローされます。Robolectric はテストの実行前に Instrumentation インスタンスをレジストリに登録するため、@RunWith(AndroidJUnit4::class) が有効になっている src/test/ では動作します。
- 4. レジストリ経由ではなく、
ApplicationProviderを介してアプリケーションの Context を取得します。ApplicationProvider.getApplicationContext()は、Application のコンテキストを取得するための単一のエントリーポイントです(ファイル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. スイートの規模が大きく、フィルタリングが必要な場合は、各テストにサイズアノテーションを付けます。 サイズは次の3種類です。
@SmallTest(<200ms、Android スタブなし)、@MediumTest(<1000ms、Robolectric 経由の Android フレームワーク)、@LargeTest(>1000ms、計測テスト)。アノテーションはandroidx.test.filters.*にあります(ファイルrunner-1.7.0/androidx/test/filters/SmallTest.javaL44-46)。ランナーの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 サブジェクトを使用します。androidx.test.ext:truth:1.7.0に同梱されている Truth には、Android 対応のSubject型が追加されています。それぞれに、static assertThat(T actual)ショートカットとstatic Subject.Factory<T, S> name()ファクトリーが用意されています。
| サブジェクト | FQCN パッケージ | アサーション対象 |
|---|---|---|
IntentSubject | androidx.test.ext.truth.content | android.content.Intent(アクション、データ、コンポーネント、エクストラ、カテゴリ、フラグ) |
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
Robolectric を使った JVM テストの場合、Gradle のプロパティによるフィルタリングが標準的な方法です:
./gradlew :app:testDebugUnitTest --tests "com.example.*" \
-PandroidTestSize=small
-e annotation com.example.SmokeTest(RunnerArgs.ANNOTATION 引数、L60)と組み合わせると、組み合わせ可能なフィルタリングを利用できます。
必須ルール
- 必須:
AndroidJUnit4はandroidx.test.ext.junit.runnersからインポートしてください。禁止:androidx.test.runner.AndroidJUnit4をインポートしないでください。これは@Deprecatedです。 - 必須:
InstrumentationRegistryはandroidx.test.platform.appからインポートしてください。禁止:androidx.test.InstrumentationRegistryをインポートしないでください。これは@Deprecatedです。 - 必須: Application コンテキストには
ApplicationProvider.getApplicationContext()を使用してください。禁止:InstrumentationRegistry.getInstrumentation().getTargetContext()を直接呼び出さないでください。ApplicationProviderが標準のエントリポイントであり、未チェックキャストを処理します。 - 必須:
viewModelScope、LaunchedEffect、またはDispatchers.Mainを利用するものに触れるテストの前に、MainDispatcherRule(または同等のDispatchers.setMain/resetMainの設定)をインストールしてください。これがないと、テストはIllegalStateException: Module with the Main dispatcher is missingでクラッシュします。 - 必須:
MainDispatcherRuleではUnconfinedTestDispatcherよりStandardTestDispatcherを優先してください。StandardTestDispatcherはrunTestのセマンティクスと一致します。先行実行されるディスパッチは、実行順序の問題を招きます。 - 必須: テストスイートをサイズでフィルタリングする場合、各テストクラスまたはメソッドにはサイズアノテーション(
@SmallTest/@MediumTest/@LargeTest)を必ず1つだけ付けてください。同じ対象に複数のサイズアノテーションを付けると、ランナーの動作は未定義になります。 - 必須: 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を宣言してください。レイアウトのインフレートやR.string.*の読み取りには、Robolectric がマージ済みリソースを必要とします。
検証
-
./gradlew :<module>:testDebugUnitTestがコンパイルされ、新しいテストスイートについてTests run: N, Failures: 0と報告されます。 -
grep -r "import androidx.test.runner.AndroidJUnit4" src/test src/androidTestの結果が NO matches になります(androidx.test.ext.junit.runners.AndroidJUnit4のみ使用できます)。 -
grep -r "import androidx.test.InstrumentationRegistry" src/test src/androidTestの結果が NO matches になります。 -
viewModelScope/Dispatchers.Mainを使用するテストでは、@get:Rule val mainDispatcherRule = MainDispatcherRule()を宣言し、runTest { … }を使用します。 - テストスイートをフィルタリングする場合は、少なくとも 1 つのテストクラスに
@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)—size、annotation、class、package、numShards/shardIndexなど、ランナーが受け付けるすべての-e <key> <value>引数です。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 です。