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:
Groovy
dependencies { def nav_version = "2.8.4" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.4" 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())
Weitere Informationen
- 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.