Ważne jest, aby przed wysłaniem aplikacji przetestować logikę nawigacyjną aplikacji, aby się upewnić, że działa ona zgodnie z oczekiwaniami.
Komponent Nawigacja obsługuje wszystkie zadania związane z zarządzaniem nawigacją między miejscami docelowymi, przekazywaniem argumentów i pracą z elementami FragmentManager
.
Te funkcje zostały już dokładnie przetestowane, więc nie musisz testować ich ponownie w aplikacji. Ważne są jednak interakcje między kodem konkretnej aplikacji we fragmentach a jego elementem NavController
.
W tym przewodniku omawiamy kilka typowych scenariuszy nawigacji oraz sposoby ich testowania.
Przetestuj nawigację po fragmentach
Aby w izolacji przetestować interakcje fragmentów z elementami NavController
, Nawigacja w wersji 2.3 i nowszych udostępnia interfejs API TestNavHostController
, który udostępnia interfejsy API do ustawiania bieżącego miejsca docelowego i weryfikowania stosu wstecznego po operacjach NavController.navigate()
.
Możesz dodać do projektu artefakt testowania nawigacji, dodając tę zależność do pliku build.gradle
modułu aplikacji:
Odlotowy
dependencies { def nav_version = "2.7.7" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
Załóżmy, że tworzysz quiz. Gra rozpoczyna się od parametru title_screen, a gdy użytkownik kliknie przycisk zagraj, otworzy się ekran in_game.
Fragment reprezentujący element title_screen może wyglądać np. tak:
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); }); } }
Aby sprawdzić, czy aplikacja prawidłowo przenosi użytkownika do ekranu in_game, gdy użytkownik kliknie Zagraj, test musi sprawdzić, czy ten fragment prawidłowo przenosi element NavController
na ekran R.id.in_game
.
Używając kombinacji właściwości FragmentScenario
, Espresso i TestNavHostController
, możesz odtworzyć warunki niezbędne do przetestowania tego scenariusza, jak pokazano w tym przykładzie:
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); } }
Powyższy przykład tworzy instancję TestNavHostController
i przypisuje ją do fragmentu. Następnie korzysta z Espresso, aby poruszać się po interfejsie, i sprawdza, czy wykonano odpowiednie działanie nawigacyjne.
Tak jak prawdziwy NavController
, musisz wywołać setGraph
, by zainicjować TestNavHostController
. W tym przykładzie testowany fragment
był punktem początkowym wykresu. TestNavHostController
udostępnia metodę setCurrentDestination
, która umożliwia ustawienie bieżącego miejsca docelowego (i opcjonalnie jego argumentów), aby przed rozpoczęciem testu NavController
był we właściwym stanie.
W przeciwieństwie do instancji NavHostController
, której używałaby NavHostFragment
, TestNavHostController
nie uruchamia podstawowego działania navigate()
(np. FragmentTransaction
, które robi FragmentNavigator
), gdy wywołujesz navigate()
– aktualizuje on tylko stan obiektu TestNavHostController
.
Przetestuj interfejs NavigationUI za pomocą FragmentScenariusz
W poprzednim przykładzie wywołanie zwrotne titleScenario.onFragment()
jest wywoływane po tym, jak fragment przejdzie przez cykl życia do stanu RESUMED
. Do tego czasu widok fragmentu został już utworzony i dołączony, dlatego może być za późno na poprawne testy. Na przykład jeśli używasz parametru NavigationUI
z widokami we fragmencie, np. z elementem Toolbar
sterowanym przez fragment, możesz wywołać metody konfiguracji za pomocą polecenia NavController
, zanim fragment osiągnie stan RESUMED
. W związku z tym trzeba znaleźć sposób na ustawienie TestNavHostController
na wcześniejszym etapie cyklu życia.
Fragment, który jest właścicielem elementu Toolbar
, można zapisać w ten sposób:
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); } }
Potrzebny jest tu element NavController
utworzony przed wywołaniem funkcji onViewCreated()
.
Poprzednie podejście onFragment()
spowodowałoby TestNavHostController
zbyt późne rozpoczęcie cyklu życia, co spowodowałoby błąd wywołania findNavController()
.
FragmentScenario
udostępnia interfejs FragmentFactory
, który pozwala rejestrować wywołania zwrotne zdarzeń cyklu życia. Można go połączyć z funkcją Fragment.getViewLifecycleOwnerLiveData()
, aby otrzymać wywołanie zwrotne, które następuje bezpośrednio po onCreateView()
, jak widać w tym przykładzie:
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; } });
Dzięki tej metodzie element NavController
jest dostępny przed wywołaniem onViewCreated()
, dzięki czemu fragment może używać metod NavigationUI
bez awarii.
Testowanie interakcji z wpisami stosu wstecznego
Podczas interakcji z wpisami w stosie wstecznym TestNavHostController
umożliwia połączenie kontrolera z własnym testem LifecycleOwner
, ViewModelStore
i OnBackPressedDispatcher
za pomocą interfejsów API, które odziedziczą z NavHostController
.
Na przykład podczas testowania fragmentu, który korzysta z ViewModel z zakresem nawigacji, musisz wywołać setViewModelStore
w 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())
Powiązane artykuły
- Tworzenie instrumentowanych testów jednostkowych – dowiedz się, jak skonfigurować zestaw z instrumentami testów i przeprowadzić testy na urządzeniu z Androidem.
- Espresso – przetestuj interfejs aplikacji za pomocą espresso.
- Reguły JUnit4 w ramach testu AndroidX Test – używaj reguł JUnit 4 z bibliotekami testów AndroidX, aby zapewnić większą elastyczność i ograniczyć powtarzalny kod wymagany w testach.
- Testowanie fragmentów aplikacji – dowiedz się, jak testować fragmenty aplikacji w izolacji za pomocą narzędzia
FragmentScenario
. - Konfigurowanie projektu na potrzeby testu AndroidX – dowiedz się, jak zadeklarować biblioteki potrzebne w plikach projektu aplikacji, aby korzystać z testu AndroidaX.