アプリのフラグメントをテストする

フラグメントは、アプリ内の再利用可能なコンテナとして機能し、1 つのユーザー インターフェース レイアウトを、さまざまなアクティビティやレイアウト構成で利用できます。フラグメントの多様性を考慮して、一貫性のあるリソース効率のよいエクスペリエンスが提供されているかどうかを検証することが重要です。

  • フラグメントの外観は、より大きな画面サイズや横向きデバイスの画面の向きをサポートするレイアウトを含め、すべてのレイアウト構成にわたって一貫している必要があります。
  • フラグメントがユーザーに表示されない限り、フラグメントのビュー階層を作成しないでください。

このドキュメントでは、フレームワークが提供する API を各フラグメントの動作を評価するテストに含める方法を説明します。

フラグメントの状態を変更する

AndroidX には、フラグメントを作成し、その状態を変更するための FragmentScenario ライブラリが用意されており、フラグメントをテストする際の実行条件を簡単にセットアップすることができます。

依存関係を宣言する

FragmentScenario を想定どおりに使用するには、下記のコード スニペットに示すように、アプリのテスト APK 内で fragment-testing アーティファクトを定義します。

app/build.gradle

    dependencies {
        def fragment_version = "1.2.4"
        // ...
        debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'
    }
    

このライブラリの現在のバージョンを確認するには、バージョンに記載されているフラグメントに関する情報をご覧ください。

フラグメントを作成する

FragmentScenario には、以下のタイプのフラグメントを起動するためのメソッドが含まれています。

また、各メソッドは、以下のタイプのフラグメントをサポートします。

  • グラフィカル フラグメント: ユーザー インターフェースを格納します。このタイプのフラグメントを起動するには、launchFragmentInContainer() を呼び出します。このフラグメントは、FragmentScenario によって、アクティビティのルートビュー コントローラにアタッチされます。このフラグメントがアタッチされていない場合、コンテナ アクティビティは空になります。
  • 非グラフィカル フラグメント(別名ヘッドレス フラグメント): 複数のアクティビティ内に格納されている情報を保存し、それに対して短期的処理を実行します。このタイプのフラグメントを起動するには、launchFragment() を呼び出します。このタイプのフラグメントは、FragmentScenario によって、完全に空のアクティビティ(ルートビューを持たないアクティビティ)にアタッチされます。

いずれかのタイプのフラグメントを起動すると、FragmentScenario により、テスト対象フラグメントは RESUMED 状態になります。この状態は、フラグメントが実行中であることを示します。テスト対象がグラフィカル フラグメントの場合、ユーザーにも表示されます。そのため、Espresso UI テストを使用して、UI 要素に関する情報を評価することができます。

次のコード スニペットは、各タイプのフラグメントを起動する方法を示しています。

グラフィカル フラグメントの例

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEventFragment() {
            // The "fragmentArgs" and "factory" arguments are optional.
            val fragmentArgs = Bundle().apply {
                putInt("selectedListItem", 0)
            }
            val factory = MyFragmentFactory()
            val scenario = launchFragmentInContainer<MyFragment>(
                    fragmentArgs, factory)
            onView(withId(R.id.text)).check(matches(withText("Hello World!")))
        }
    }
    

非グラフィカル フラグメントの例

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEventFragment() {
            // The "fragmentArgs" and "factory" arguments are optional.
            val fragmentArgs = Bundle().apply {
                putInt("numElements", 0)
            }
            val factory = MyFragmentFactory()
            val scenario = launchFragment<MyFragment>(fragmentArgs, factory)
        }
    }
    

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

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

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

FragmentScenario クラスによって、テスト対象フラグメントが再作成されると、このフラグメントは、再作成される前のライフサイクル状態に戻ります。

フラグメントを新しい状態にする

アプリの UI テストを行う場合は通常、テスト対象フラグメントを起動して再作成するだけで十分です。しかし、詳細な単体テストを行う場合は、フラグメントのライフサイクル状態が遷移する際のフラグメントの動作を評価することがあります。

フラグメントを別のライフサイクル状態にするには、moveToState() を呼び出します。このメソッドは、各状態を引数として CREATEDSTARTEDRESUMEDDESTROYED をサポートしています。このアクションは、フラグメントを格納しているコンテナ アクティビティが、別のアプリやシステムのアクションによって中断されたことで状態が変化する状況をシミュレーションします。

moveToState() の使用例を次のコード スニペットに示します。

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEventFragment() {
            val scenario = launchFragmentInContainer<MyFragment>()
            scenario.moveToState(State.CREATED)
        }
    }
    

フラグメント内でアクションをトリガーする

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

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEventFragment() {
            val scenario = launchFragmentInContainer<MyFragment>()
            onView(withId(R.id.refresh))
                    .perform(click())
        }
    }
    

オプション メニューの選択に対するレスポンスなど、フラグメント自体に対してメソッドを呼び出す必要がある場合は、FragmentAction を実装すると安全に呼び出すことができます。

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEventFragment() {
            val scenario = launchFragmentInContainer<MyFragment>()
            scenario.onFragment(fragment ->
                fragment.onOptionsItemSelected(clickedItem) {
                    // Update fragment's state based on selected item.
                }
            }
        }
    }
    

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

FragmentScenario は、ダイアログのテストもサポートしています。ダイアログはグラフィカル フラグメントのインスタンスですが、launchFragment() メソッドを使用して、ダイアログの要素を、ダイアログを起動するアクティビティの中ではなく、ダイアログ自体の中に格納するようにします。

ダイアログ消去プロセスをテストするコード スニペットを以下に示します。

    @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.requireFragmentManager().executePendingTransactions()
                    assertThat(fragment.dialog).isNull()
                }

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