mcpField
C
configuring-junit4-on-android

configuring-junit4-on-android

github.com/skydovesテスト・品質33974 回インストール22 週間前に更新
リポジトリ

configuring-junit4-on-android について

Android Kotlin のテスト作成者向けの JUnit4 設定ガイドです。標準の AndroidJUnit4 ランナーとレジストリ、アプリケーションコンテキスト、サイズ注釈、Android 用 Truth 断言、メインディスパッチャールールを扱います。

主な機能

  1. 01標準の AndroidJUnit4 ランナーを選択
  2. 02InstrumentationRegistry と ApplicationProvider の使用
  3. 03サイズ注釈によるテスト分類と絞り込み
  4. 04Truth による Android オブジェクトの検証
  5. 05Dispatchers.Main 用 MainDispatcherRule の設定

活用シーン

  1. 01AndroidJUnit4 や InstrumentationRegistry のインポート問題を修正
  2. 02Instrumentation の引数を使うテストを設定
  3. 03small、medium、large のサイズでテストを絞り込み
  4. 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. が付かないもの)は、:monitor 1.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.java L29-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.java L44-46)。ランナーの RunnerArgs.SIZE(runner-1.7.0/androidx/test/internal/runner/RunnerArgs.java L58)は、-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 パッケージアサーション対象
IntentSubjectandroidx.test.ext.truth.contentandroid.content.Intent(アクション、データ、コンポーネント、エクストラ、カテゴリ、フラグ)
BundleSubject / BaseBundleSubject / PersistableBundleSubjectandroidx.test.ext.truth.osBundle / BaseBundle / PersistableBundle のキー、型、値
ParcelableSubjectandroidx.test.ext.truth.osParcelable(ライターとリーダーを検証するため、Parcel を介してラウンドトリップします)
LocationSubjectandroidx.test.ext.truth.locationandroid.location.Location
NotificationSubject / NotificationActionSubject / PendingIntentSubjectandroidx.test.ext.truth.appNotification ファミリー
MotionEventSubject / PointerCoordsSubject / PointerPropertiesSubjectandroidx.test.ext.truth.viewMotionEvent ファミリー
SparseBooleanArraySubjectandroidx.test.ext.truth.utilSparseBooleanArray

慣用的なインポートと呼び出し方:

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-test 1.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.java L56)。インストルメント化テストのバリアントについては、../../../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 — 正規の androidx MainDispatcherRule のソースです。ここで提供されているものと同一のパターンです。
  • クロスセット:../../../kotlin/kotlin-test/writing-tests-with-kotlin-test/SKILL.md — これらの JUnit4 テスト内で使う、フレームワークに依存しない kotlin.test のアサーション(assertEquals、assertFailsWith、@BeforeTest)について説明しています。また、commonTest で使用できる唯一のアサーション API です。

コメント