Navigation testen

Es ist wichtig, vor dem Versenden die Navigationslogik Ihrer App zu testen, um zu überprüfen, ob Ihre App wie erwartet funktioniert.

Die Komponente „Navigation“ übernimmt die Verwaltung der Navigation zwischen Ziele übergeben, Argumente übergeben und mit dem FragmentManager Diese Funktionen wurden bereits eingehend getestet, sodass es nicht erforderlich ist, in Ihrer App erneut aufrufen. Wichtig ist jedoch, die Interaktionen zu testen, zwischen dem anwendungsspezifischen Code in Ihren Fragmenten und deren NavController In diesem Leitfaden werden einige gängige Navigationsszenarien und Tests erläutert.

Fragmentnavigation testen

Um Fragmentinteraktionen mit ihrem NavController isoliert zu testen, Navigation 2.3 und höher bietet eine TestNavHostController die APIs zum Festlegen des aktuellen Ziels und zur Überprüfung des Stapel nach NavController.navigate() Geschäftsabläufe.

Sie können Ihrem Projekt das Artefakt „Navigation Testing“ hinzufügen, indem Sie das Artefakt folgende Abhängigkeit in der Datei build.gradle deines App-Moduls:

Cool

dependencies {
  def nav_version = "2.8.0"

  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}

Kotlin

dependencies {
  val nav_version = "2.8.0"

  androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
}

Angenommen, Sie entwickeln ein Quizspiel. Das Spiel beginnt mit einer title_screen und ruft einen in_game-Bildschirm auf, wenn der Nutzer auf den Bildschirm klickt. spielen.

Das Fragment, das title_screen darstellt, könnte in etwa so aussehen:

Kotlin

class TitleScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ) = inflater.inflate(R.layout.fragment_title_screen, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById<Button>(R.id.play_btn).setOnClickListener {
            view.findNavController().navigate(R.id.action_title_screen_to_in_game)
        }
    }
}

Java

public class TitleScreen extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_title_screen, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        view.findViewById(R.id.play_btn).setOnClickListener(v -> {
            Navigation.findNavController(view).navigate(R.id.action_title_screen_to_in_game);
        });
    }
}

Um zu testen, ob die App den Nutzer ordnungsgemäß zum in_game-Bildschirm weiterleitet, der Nutzer auf Wiedergabe klickt, muss im Test überprüft werden, ob dieses Fragment NavController korrekt in den R.id.in_game-Bildschirm verschoben.

Mit einer Kombination aus FragmentScenario, Espresso, und TestNavHostController können Sie die für Tests erforderlichen Bedingungen für dieses Szenario, wie im folgenden Beispiel gezeigt:

Kotlin

@RunWith(AndroidJUnit4::class)
class TitleScreenTest {

    @Test
    fun testNavigationToInGameScreen() {
        // Create a TestNavHostController
        val navController = TestNavHostController(
            ApplicationProvider.getApplicationContext())

        // Create a graphical FragmentScenario for the TitleScreen
        val titleScenario = launchFragmentInContainer<TitleScreen>()

        titleScenario.onFragment { fragment ->
            // Set the graph on the TestNavHostController
            navController.setGraph(R.navigation.trivia)

            // Make the NavController available via the findNavController() APIs
            Navigation.setViewNavController(fragment.requireView(), navController)
        }

        // Verify that performing a click changes the NavController’s state
        onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click())
        assertThat(navController.currentDestination?.id).isEqualTo(R.id.in_game)
    }
}

Java

@RunWith(AndroidJUnit4.class)
public class TitleScreenTestJava {

    @Test
    public void testNavigationToInGameScreen() {

        // Create a TestNavHostController
        TestNavHostController navController = new TestNavHostController(
            ApplicationProvider.getApplicationContext());

        // Create a graphical FragmentScenario for the TitleScreen
        FragmentScenario<TitleScreen> titleScenario = FragmentScenario.launchInContainer(TitleScreen.class);

        titleScenario.onFragment(fragment ->
                // Set the graph on the TestNavHostController
                navController.setGraph(R.navigation.trivia);

                // Make the NavController available via the findNavController() APIs
                Navigation.setViewNavController(fragment.requireView(), navController)
        );

        // Verify that performing a click changes the NavController’s state
        onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click());
        assertThat(navController.currentDestination.id).isEqualTo(R.id.in_game);
    }
}

Im obigen Beispiel wird eine Instanz von TestNavHostController erstellt und ihr zugewiesen mit dem Fragment. Dann verwendet es Espresso, um die Benutzeroberfläche zu steuern, und überprüft, entsprechende Navigationsaktion durchgeführt wird.

Genau wie bei einem echten NavController müssen Sie zum Initialisieren setGraph aufrufen. TestNavHostController. In diesem Beispiel wurde das getestete Fragment Startziel unseres Diagramms. TestNavHostController bietet eine setCurrentDestination mit der Sie das aktuelle Ziel (und optional Argumente für dieses Ziel), sodass NavController im Feld bevor der Test beginnt.

Im Gegensatz zu einer NavHostController-Instanz, die ein NavHostFragment verwenden würde, TestNavHostController löst nicht den zugrunde liegenden navigate() aus Verhalten (z. B. FragmentTransaction, das FragmentNavigator tut) wenn Sie navigate() aufrufen, wird nur der Status der TestNavHostController

Navigation UI mit FragmentSzenario testen

Im vorherigen Beispiel wurde der Callback für titleScenario.onFragment() bereitgestellt. wird aufgerufen, nachdem das Fragment seinen Lebenszyklus zum RESUMED Bundesstaat. Zu diesem Zeitpunkt wurde die Ansicht des Fragments bereits erstellt und angehängt. Daher ist es im Lebenszyklus möglicherweise zu spät für einen ordnungsgemäßen Test. Wenn Sie beispielsweise NavigationUI mit Ansichten in Ihrem Fragment, beispielsweise mit einer von Toolbar gesteuerten nach Ihrem Fragment suchen, können Sie Einrichtungsmethoden mit Ihrer NavController aufrufen, bevor erreicht das Fragment den Status RESUMED. Sie müssen also eine Möglichkeit haben, TestNavHostController früher im Lebenszyklus.

Ein Fragment, das eine eigene Toolbar besitzt, kann so geschrieben werden:

Kotlin

class TitleScreen : Fragment(R.layout.fragment_title_screen) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val navController = view.findNavController()
        view.findViewById<Toolbar>(R.id.toolbar).setupWithNavController(navController)
    }
}

Java

public class TitleScreen extends Fragment {
    public TitleScreen() {
        super(R.layout.fragment_title_screen);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        NavController navController = Navigation.findNavController(view);
        view.findViewById(R.id.toolbar).setupWithNavController(navController);
    }
}

Hier benötigen wir das NavController, das zum Zeitpunkt des Aufrufs von onViewCreated() erstellt wurde. Bei Verwendung des vorherigen Ansatzes von onFragment() würde der TestNavHostController festgelegt werden. im Lebenszyklus zu spät, sodass der findNavController()-Aufruf fehlschlägt.

FragmentScenario bietet ein FragmentFactory Schnittstelle, mit der Callbacks für Lebenszyklusereignisse registriert werden können. Dies kann mit Fragment.getViewLifecycleOwnerLiveData() kombiniert werden, um Folgendes zu erhalten: -Callback, der sofort auf onCreateView() folgt, wie im Folgenden gezeigt: Beispiel:

Kotlin

val scenario = launchFragmentInContainer {
    TitleScreen().also { fragment ->

        // In addition to returning a new instance of our Fragment,
        // get a callback whenever the fragment’s view is created
        // or destroyed so that we can set the NavController
        fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
            if (viewLifecycleOwner != null) {
                // The fragment’s view has just been created
                navController.setGraph(R.navigation.trivia)
                Navigation.setViewNavController(fragment.requireView(), navController)
            }
        }
    }
}

Java

FragmentScenario<TitleScreen> scenario =
FragmentScenario.launchInContainer(
       TitleScreen.class, null, new FragmentFactory() {
    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader,
            @NonNull String className,
            @Nullable Bundle args) {
        TitleScreen titleScreen = new TitleScreen();

        // In addition to returning a new instance of our fragment,
        // get a callback whenever the fragment’s view is created
        // or destroyed so that we can set the NavController
        titleScreen.getViewLifecycleOwnerLiveData().observeForever(new Observer<LifecycleOwner>() {
            @Override
            public void onChanged(LifecycleOwner viewLifecycleOwner) {

                // The fragment’s view has just been created
                if (viewLifecycleOwner != null) {
                    navController.setGraph(R.navigation.trivia);
                    Navigation.setViewNavController(titleScreen.requireView(), navController);
                }

            }
        });
        return titleScreen;
    }
});

Mit dieser Technik ist der NavController verfügbar, bevor onViewCreated() wird aufgerufen, wodurch das Fragment die Verwendung von NavigationUI-Methoden ermöglicht. ohne abstürzen zu müssen.

Interaktionen mit Back-Stack-Einträgen testen

Wenn Sie mit den Back-Stack-Einträgen interagieren, gilt Folgendes: Mit dem TestNavHostController kannst du den Controller mit deinem eigenen LifecycleOwner, ViewModelStore und OnBackPressedDispatcher testen, unter Verwendung der APIs, von denen sie übernommen wird NavHostController

Wenn Sie beispielsweise ein Fragment testen, ViewModel auf Navigationsebene du musst anrufen setViewModelStore am TestNavHostController:

Kotlin

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

// This allows fragments to use by navGraphViewModels()
navController.setViewModelStore(ViewModelStore())

Java

TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());

// This allows fragments to use new ViewModelProvider() with a NavBackStackEntry
navController.setViewModelStore(new ViewModelStore())
  • Instrumentierte Einheitentests erstellen Hier erfährst du, wie du deine instrumentierte Testsuite einrichtest und Tests auf einem Android-Gerät durchführst .
  • Espresso: Testen Sie die Benutzeroberfläche Ihrer App. mit Espresso.
  • JUnit4-Regeln mit AndroidX-Test – JUnit 4 verwenden mit den AndroidX-Testbibliotheken kombinieren, um mehr Flexibilität zu bieten und den in Tests erforderlichen Boilerplate-Code.
  • Anwendungsfragmente testen: Hier erfahren Sie, wie Sie Ihre Anwendungsfragmente mit FragmentScenario isoliert testen.
  • Projekt für AndroidX-Test einrichten – weitere Informationen wie erforderliche Bibliotheken in den Projektdateien Ihrer App deklariert werden, um AndroidX zu verwenden Testen.