フラグメントをテストする

このトピックでは、個々のフラグメントの動作を評価するテストに、フレームワークが提供する API を組み込む方法について説明します。

フラグメントは、アプリ内の再利用可能なコンテナとして機能し、1 つのユーザー インターフェース レイアウトをさまざまなアクティビティとレイアウト構成で表示することを可能にします。フラグメントは多種多様であるため、それらがリソース効率の良い一貫したエクスペリエンスを提供するかどうかを検証することが重要です。次の点に注意してください。

  • フラグメントは、特定の親アクティビティまたはフラグメントに依存してはなりません。
  • フラグメントがユーザーに表示される場合以外は、フラグメントのビュー階層を作成すべきではありません。

AndroidX の fragment-testing ライブラリには、フラグメントをテストするための条件の設定に役立つ FragmentScenario クラスが用意されています。これにより、フラグメントを作成し、その Lifecycle.State を変更できます。

依存関係の宣言

FragmentScenario を使用するには、アプリの build.gradle ファイルで、debugImplementation を使用して fragment-testing アーティファクトを定義します。次の例をご覧ください。

Groovy

dependencies {
    def fragment_version = "1.6.2"

    debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}

Kotlin

dependencies {
    val fragment_version = "1.6.2"

    debugImplementation("androidx.fragment:fragment-testing:$fragment_version")
}

このページのテスト例では、Espresso ライブラリと Truth ライブラリのアサーションを使用しています。その他の利用可能なテスト ライブラリとアサーション ライブラリについては、AndroidX Test 用にプロジェクトをセットアップするをご覧ください。

フラグメントを作成する

FragmentScenario には、テストでフラグメントを開始するための以下のメソッドが含まれています。

  • launchInContainer(): フラグメントのユーザー インターフェースをテストします。FragmentScenario は、フラグメントをアクティビティのルートビュー コントローラにアタッチします。フラグメントをアタッチしないと、コンテナ アクティビティは空になります。
  • launch(): ユーザー インターフェースがないフラグメントをテストします。FragmentScenario は、このタイプのフラグメントを、ルートビューを持たない空のアクティビティにアタッチします。

こうしたフラグメントのいずれかを開始すると、FragmentScenario により、テスト対象のフラグメントが指定された状態に遷移します。この状態はデフォルトでは RESUMED ですが、initialState 引数でオーバーライドできます。RESUMED 状態は、フラグメントが実行中で、ユーザーに表示されていることを示します。その UI 要素に関する情報は、Espresso UI テストで評価できます。

次のコード例は、それぞれのメソッドを使用してフラグメントを開始する方法を示しています。

launchInContainer() の例

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" argument is optional.
        val fragmentArgs = bundleOf(“selectedListItem” to 0)
        val scenario = launchFragmentInContainer<EventFragment>(fragmentArgs)
        ...
    }
}

launch() の例

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" arguments are optional.
        val fragmentArgs = bundleOf("numElements" to 0)
        val scenario = launchFragment<EventFragment>(fragmentArgs)
        ...
    }
}

依存関係を提供する

フラグメントに依存関係が存在する場合、launchInContainer() メソッドまたは launch() メソッドに対してカスタム FragmentFactory を指定することにより、それらの依存関係のテスト バージョンを提供できます。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val someDependency = TestDependency()
        launchFragmentInContainer {
            EventFragment(someDependency)
        }
        ...
    }
}

FragmentFactory を使用してフラグメントに依存関係を提供する方法の詳細については、フラグメント マネージャーをご覧ください。

フラグメントを新しい状態に遷移させる

一般的に、アプリの UI テストでは、テスト対象のフラグメントを開始して、RESUMED 状態からテストを開始すれば十分です。しかし、よりきめ細かい単体テストでは、ライフサイクル状態の遷移に応じたフラグメントの動作を評価する場合もあります。初期状態を指定するには、initialState 引数をいずれかの launchFragment*() 関数に渡します。

フラグメントを別のライフサイクル状態に遷移させるには、moveToState() を呼び出します。このメソッドでは、CREATEDSTARTEDRESUMEDDESTROYED の各状態を引数として使用できます。このメソッドにより、フラグメント、またはフラグメントを含むアクティビティの状態がなんらかの理由で変化する状況をシミュレートできます。

次の例では、INITIALIZED 状態でテスト フラグメントを開始し、RESUMED 状態に遷移させています。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>(
            initialState = Lifecycle.State.INITIALIZED
        )
        // EventFragment has gone through onAttach(), but not onCreate().
        // Verify the initial state.
        scenario.moveToState(Lifecycle.State.RESUMED)
        // EventFragment moves to CREATED -> STARTED -> RESUMED.
        ...
    }
}

フラグメントを再作成する

リソースが乏しいデバイスでアプリが実行されている場合、フラグメントを含むアクティビティがシステムによって破棄されることがあります。このような状況では、ユーザーがフラグメントに戻ろうとしたときに、アプリがフラグメントを再作成する必要があります。この状況をシミュレートするには、recreate() を呼び出します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.recreate()
        ...
    }
}

FragmentScenario.recreate() は、フラグメントとそのホストを破棄した後、再作成します。FragmentScenario クラスによりテスト対象のフラグメントが再作成されると、フラグメントは破棄される前のライフサイクル状態に戻ります。

UI フラグメントを操作する

テスト対象のフラグメント内で UI アクションをトリガーするには、Espresso ビュー マッチャーを使用してビュー内の要素を操作します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        onView(withId(R.id.refresh)).perform(click())
        // Assert some expected behavior
        ...
    }
}

フラグメント自体でメソッドを呼び出す必要がある場合(たとえば、オプション メニューの選択操作に応答する場合)は、安全にメソッドを呼び出すために、FragmentScenario.onFragment() を使用してフラグメントへの参照を取得し、FragmentAction に渡します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.onFragment { fragment ->
            fragment.myInstanceMethod()
        }
    }
}

ダイアログ アクションをテストする

FragmentScenario を使用して、ダイアログ フラグメントもテストできます。ダイアログ フラグメントには UI 要素が含まれていますが、レイアウトはアクティビティ自体ではなく別のウィンドウに配置されます。したがって、ダイアログ フラグメントをテストするには FragmentScenario.launch() を使用します。

次の例では、ダイアログを閉じるプロセスをテストしています。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testDismissDialogFragment() {
        // Assumes that "MyDialogFragment" extends the DialogFragment class.
        with(launchFragment<MyDialogFragment>()) {
            onFragment { fragment ->
                assertThat(fragment.dialog).isNotNull()
                assertThat(fragment.requireDialog().isShowing).isTrue()
                fragment.dismiss()
                fragment.parentFragmentManager.executePendingTransactions()
                assertThat(fragment.dialog).isNull()
            }
        }

        // Assumes that the dialog had a button
        // containing the text "Cancel".
        onView(withText("Cancel")).check(doesNotExist())
    }
}