Fragmente testen

In diesem Thema wird beschrieben, wie Sie vom Framework bereitgestellte APIs in Tests einbinden, die das Verhalten jedes Fragments bewerten.

Fragmente dienen als wiederverwendbare Container in Ihrer App, sodass Sie dasselbe Layout der Benutzeroberfläche in verschiedenen Aktivitäten und Layoutkonfigurationen präsentieren können. Angesichts der Vielseitigkeit von Fragmenten ist es wichtig zu prüfen, ob sie eine konsistente und ressourceneffiziente Umgebung bieten. Beachten Sie Folgendes:

  • Das Fragment sollte nicht von einer bestimmten übergeordneten Aktivität oder einem spezifischen übergeordneten Fragment abhängen.
  • Sie sollten die Ansichtshierarchie eines Fragments nur erstellen, wenn das Fragment für den Nutzer sichtbar ist.

Damit die Bedingungen für die Durchführung dieser Tests leichter festgelegt werden können, stellt die fragment-testing-Bibliothek von AndroidX die Klasse FragmentScenario bereit, um Fragmente zu erstellen und deren Lifecycle.State zu ändern.

Abhängigkeiten deklarieren

Definieren Sie zur Verwendung von FragmentScenario das Artefakt fragment-testing in der Datei build.gradle der Anwendung mit debugImplementation, wie im folgenden Beispiel gezeigt:

Groovig

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")
}

Für die Testbeispiele auf dieser Seite werden Assertions aus den Bibliotheken Espresso und Truth verwendet. Informationen zu anderen verfügbaren Test- und Assertion-Bibliotheken finden Sie unter Projekt für AndroidX Test einrichten.

Fragment erstellen

FragmentScenario enthält die folgenden Methoden zum Starten von Fragmenten in Tests:

  • launchInContainer() zum Testen der Benutzeroberfläche eines Fragments FragmentScenario hängt das Fragment an den Root-Ansicht-Controller einer Aktivität an. Ansonsten ist diese Aktivität leer.
  • launch() zum Testen ohne die Benutzeroberfläche des Fragments. FragmentScenario hängt diese Art von Fragment an eine leere Aktivität an, die keine Stammansicht hat.

Nach dem Start eines dieser Fragmenttypen bringt FragmentScenario das zu testende Fragment in einen bestimmten Zustand. Der Status ist standardmäßig RESUMED. Sie können ihn aber mit dem Argument initialState überschreiben. Der Status RESUMED gibt an, dass das Fragment ausgeführt wird und für den Nutzer sichtbar ist. Informationen zu den UI-Elementen können mithilfe von Espresso-UI-Tests ausgewertet werden.

Die folgenden Codebeispiele zeigen, wie Sie das Fragment mit der jeweiligen Methode starten:

launchInContainer()-Beispiel

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

Beispiel für „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)
        ...
    }
}

Abhängigkeiten bereitstellen

Wenn die Fragmente Abhängigkeiten haben, können Sie Testversionen dieser Abhängigkeiten durch eine benutzerdefinierte FragmentFactory für die Methoden launchInContainer() oder launch() bereitstellen.

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

Weitere Informationen zur Verwendung von FragmentFactory zum Bereitstellen von Abhängigkeiten für Fragmente finden Sie unter Fragmentmanager.

Fragment in einen neuen Zustand verschieben

Bei den UI-Tests Ihrer Anwendung reicht es in der Regel aus, das zu testende Fragment zu starten und mit dem Test im Status RESUMED zu beginnen. In detaillierteren Einheitentests können Sie jedoch auch das Verhalten des Fragments beim Übergang von einem Lebenszyklusstatus in einen anderen bewerten. Sie können den Anfangszustand angeben, indem Sie das Argument initialState an eine der launchFragment*()-Funktionen übergeben.

Wenn Sie das Fragment in einen anderen Lebenszyklusstatus verschieben möchten, rufen Sie moveToState() auf. Diese Methode unterstützt die folgenden Status als Argumente: CREATED, STARTED, RESUMED und DESTROYED. Diese Methode simuliert eine Situation, in der das Fragment oder die Aktivität, die das Fragment enthält, ihren Status aus irgendeinem Grund ändert.

Im folgenden Beispiel wird ein Testfragment mit dem Status INITIALIZED gestartet und dann in den Status RESUMED verschoben:

@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.
        ...
    }
}

Fragment neu erstellen

Wenn Ihre App auf einem Gerät mit wenigen Ressourcen ausgeführt wird, löscht das System möglicherweise die Aktivität, die Ihr Fragment enthält. In diesem Fall muss Ihre Anwendung das Fragment neu erstellen, wenn der Nutzer zu ihm zurückkehrt. Rufen Sie recreate() auf, um diese Situation zu simulieren:

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

FragmentScenario.recreate() löscht das Fragment und seinen Host und erstellt sie neu. Wenn die Klasse FragmentScenario das zu testende Fragment neu erstellt, kehrt es in den Lebenszyklusstatus zurück, in dem es sich vor der Löschung befand.

Mit UI-Fragmenten interagieren

Um UI-Aktionen in dem zu testenden Fragment auszulösen, verwenden Sie Espresso-View-Matcher, um mit Elementen in der Ansicht zu interagieren:

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

Wenn Sie eine Methode für das Fragment selbst aufrufen müssen, z. B. auf eine Auswahl im Optionsmenü reagieren, können Sie bedenkenlos mit FragmentScenario.onFragment() einen Verweis auf das Fragment abrufen und einen FragmentAction übergeben:

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

Dialogfeldaktionen testen

FragmentScenario unterstützt auch das Testen von Dialogfragmenten. Auch wenn Dialogfragmente UI-Elemente haben, wird ihr Layout in einem separaten Fenster statt in der Aktivität selbst dargestellt. Verwenden Sie daher FragmentScenario.launch(), um Dialogfragmente zu testen.

Im folgenden Beispiel wird der Prozess zum Schließen eines Dialogfelds getestet:

@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())
    }
}