Es ist wichtig, die Navigationslogik Ihrer App vor der Veröffentlichung zu testen, um sicherzustellen, dass Ihre Anwendung wie erwartet funktioniert.
Die Navigation-Komponente übernimmt die gesamte Arbeit für die Verwaltung der Navigation zwischen Zielen, das Übergeben von Argumenten und die Arbeit mit dem FragmentManager. Diese Funktionen werden bereits gründlich getestet, sodass Sie sie in Ihrer App nicht noch einmal testen müssen. Wichtig ist jedoch, die Interaktionen zwischen dem app-spezifischen Code in Ihren Fragmenten und ihren NavController zu testen.
Isoliert testen
Um Fragmentinteraktionen mit ihrem NavController isoliert zu testen, bietet Navigation 2.3 und höher ein TestNavHostController mit APIs zum Festlegen des aktuellen Ziels und zum Prüfen des Backstacks nach NavController.navigate()-Vorgängen.
Sie können das Navigation Testing-Artefakt Ihrem Projekt hinzufügen, indem Sie die folgende Abhängigkeit in die Datei build.gradle Ihres App-Moduls einfügen:
Groovy
dependencies { def nav_version = "2.9.8" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.8" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
Quizspiele Das Spiel beginnt mit einem Titelbildschirm und wechselt zu einem In-Game-Bildschirm, wenn der Nutzer auf „Spielen“ klickt.
Das Fragment für den title_screen könnte 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 korrekt zum Bildschirm in_game weiterleitet, wenn er auf Play klickt, muss in Ihrem Test geprüft werden, ob dieses Fragment das NavController korrekt zum Bildschirm R.id.in_game verschiebt.
Mit einer Kombination aus FragmentScenario, Espresso und TestNavHostController können Sie die Bedingungen für das Testen dieses Szenarios nachbilden, 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 using 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
fun 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 using 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 vorherigen Beispiel wird eine Instanz von TestNavHostController erstellt und dem Fragment zugewiesen. Anschließend wird Espresso verwendet, um die Benutzeroberfläche zu steuern und zu prüfen, ob die entsprechende Navigationsaktion ausgeführt wird.
Genau wie bei einem echten NavController müssen Sie setGraph aufrufen, um das TestNavHostController zu initialisieren. In diesem Beispiel war das getestete Fragment das Startziel unseres Diagramms. TestNavHostController bietet eine setCurrentDestination-Methode, mit der Sie das aktuelle Ziel (und optional Argumente für dieses Ziel) festlegen können, sodass sich NavController vor Beginn des Tests im richtigen Zustand befindet.
Im Gegensatz zu einer NavHostController-Instanz, die von einem NavHostFragment verwendet wird, löst TestNavHostController beim Aufrufen von navigate() nicht das zugrunde liegende navigate()-Verhalten aus (z. B. das FragmentTransaction, das FragmentNavigator auslöst). Es wird nur der Status des TestNavHostController aktualisiert.
NavigationUI mit FragmentScenario testen
Im vorherigen Beispiel wird der für titleScenario.onFragment() bereitgestellte Callback aufgerufen, nachdem das Fragment seinen Lebenszyklus bis zum Status RESUMED durchlaufen hat. Zu diesem Zeitpunkt wurde die Ansicht des Fragments bereits erstellt und angehängt. Es ist also möglicherweise zu spät im Lebenszyklus, um sie richtig zu testen. Wenn Sie beispielsweise NavigationUI mit Ansichten in Ihrem Fragment verwenden, z. B. mit einem Toolbar, das von Ihrem Fragment gesteuert wird, können Sie Einrichtungsmethoden mit Ihrem NavController aufrufen, bevor das Fragment den Status RESUMED erreicht. Sie benötigen also eine Möglichkeit, Ihre TestNavHostController früher im Lebenszyklus festzulegen.
Ein Fragment, das ein eigenes Toolbar hat, 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 die NavController, die bis zum Aufruf von onViewCreated() erstellt wurde. Wenn wir den vorherigen Ansatz mit onFragment() verwenden, wird TestNavHostController zu spät im Lebenszyklus festgelegt, sodass der findNavController()-Aufruf fehlschlägt.
FragmentScenario bietet eine FragmentFactory-Schnittstelle, mit der Callbacks für Lifecycle-Ereignisse registriert werden können. Dies kann mit Fragment.getViewLifecycleOwnerLiveData() kombiniert werden, um einen Callback zu erhalten, der unmittelbar auf onCreateView() folgt, wie im folgenden Beispiel gezeigt:
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;
}
});
Durch diese Technik ist NavController verfügbar, bevor onViewCreated() aufgerufen wird. So kann das Fragment NavigationUI-Methoden verwenden, ohne abzustürzen.
Interaktionen mit Backstack-Einträgen testen
Bei der Interaktion mit den Einträgen im Backstack können Sie mit TestNavHostController den Controller über die APIs, die er von NavHostController erbt, mit Ihren eigenen Test-LifecycleOwner, ViewModelStore und OnBackPressedDispatcher verbinden.
Wenn Sie beispielsweise ein Fragment testen, das ein ViewModel mit Navigationsbereich verwendet, müssen Sie setViewModelStore für TestNavHostController aufrufen:
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 erfahren Sie, wie Sie Ihre instrumentierte Testsuite einrichten und Tests auf einem Android-Gerät ausführen.
- Espresso: Mit Espresso können Sie die Benutzeroberfläche Ihrer App testen.
- JUnit4-Regeln mit AndroidX Test: Verwenden Sie JUnit 4-Regeln mit den AndroidX Test-Bibliotheken, um mehr Flexibilität zu bieten und den in Tests erforderlichen Boilerplate-Code zu reduzieren.
- App-Fragmente testen: Hier erfahren Sie, wie Sie die Fragmente Ihrer App mit
FragmentScenarioisoliert testen. - Projekt für AndroidX Test einrichten: Hier erfahren Sie, wie Sie die erforderlichen Bibliotheken in den Projektdateien Ihrer App deklarieren, um AndroidX Test zu verwenden.