È importante testare la logica di navigazione dell'app prima di spedirla per verificare che l'app funzioni come previsto.
Il componente Navigazione gestisce tutte le attività di gestione della navigazione tra
destinazioni, passare argomenti e lavorare con
FragmentManager
Queste funzionalità sono già rigorosamente testate, quindi non è necessario testare
li di nuovo nell'app. È importante verificare, tuttavia, che le interazioni
tra il codice specifico dell'app nei tuoi frammenti e i relativi
NavController
Questa guida illustra alcuni scenari di navigazione comuni e spiega come testarli.
Navigazione dei frammenti di test
Per testare le interazioni dei frammenti con il relativo NavController
in isolamento,
La versione 2.3 e le versioni successive offrono un
TestNavHostController
che fornisce API per impostare la destinazione attuale e verificare il
pila dopo
NavController.navigate()
operazioni.
Puoi aggiungere l'artefatto del test di navigazione al progetto aggiungendo l'elemento
seguente dipendenza nel file build.gradle
del modulo dell'app:
Alla moda
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") }
Supponiamo che tu stia creando un gioco a quiz. Il gioco inizia con title_screen e passa a una schermata in_game quando l'utente fa clic riprodurre.
Il frammento che rappresenta title_screen potrebbe avere un aspetto simile al seguente:
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); }); } }
Per verificare che l'app indirizzi correttamente l'utente alla schermata in_game quando
l'utente fa clic su Riproduci, il test deve verificare che questo frammento
sposta correttamente NavController
nella schermata R.id.in_game
.
Utilizzando una combinazione di FragmentScenario
, Espresso,
e TestNavHostController
, puoi ricreare le condizioni necessarie per
questo scenario, come illustrato nell'esempio seguente:
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); } }
L'esempio precedente crea un'istanza di TestNavHostController
e la assegna
al frammento. Quindi utilizza Espresso per gestire l'UI e verifica che
venga intrapresa l'azione di navigazione appropriata.
Proprio come un NavController
reale, devi chiamare setGraph
per inizializzare
TestNavHostController
. In questo esempio, il frammento sottoposto a test è stato
la destinazione iniziale del grafico. TestNavHostController
offre un
setCurrentDestination
che ti consente di impostare la destinazione corrente (e, facoltativamente,
per quella destinazione) in modo che NavController
sia nell'
lo stato corretto prima di iniziare il test.
A differenza di un'istanza NavHostController
utilizzata da un NavHostFragment
,
TestNavHostController
non attiva navigate()
sottostante
(ad esempio i FragmentTransaction
di FragmentNavigator
)
quando chiami navigate()
, l'aggiornamento aggiorna solo lo stato del
TestNavHostController
.
Testa l'interfaccia utente di navigazione con lo scenario Fragment
Nell'esempio precedente, il callback fornito a titleScenario.onFragment()
viene chiamato dopo che il frammento ha completato il proprio ciclo di vita
RESUMED
stato. A questo punto la vista del frammento è già stata creata e allegata,
quindi potrebbe essere troppo tardi nel ciclo di vita per eseguire correttamente i test. Ad esempio, quando utilizzi
NavigationUI
con visualizzazioni nel frammento, ad esempio con un elemento controllato da Toolbar
dal frammento, puoi richiamare i metodi di configurazione con NavController
prima
il frammento raggiunge lo stato RESUMED
. È quindi necessario un modo per impostare
TestNavHostController
nelle prime fasi del ciclo di vita.
Un frammento proprietario di un proprio Toolbar
può essere scritto come segue:
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); } }
Qui ci serve il NavController
creato quando verrà chiamato onViewCreated()
.
L'utilizzo dell'approccio precedente di onFragment()
imposterebbe le nostre TestNavHostController
troppo tardi nel ciclo di vita, causando l'esito negativo della chiamata findNavController()
.
FragmentScenario
offre un
FragmentFactory
che può essere utilizzata per registrare callback per eventi del ciclo di vita. Questo può
da combinare con Fragment.getViewLifecycleOwnerLiveData()
per ricevere un
che segue immediatamente onCreateView()
, come illustrato nell'esempio seguente
esempio:
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; } });
Utilizzando questa tecnica, il NavController
è disponibile prima
Viene chiamato il metodo onViewCreated()
, consentendo al frammento di usare metodi NavigationUI
senza arresti anomali.
Test delle interazioni con voci back stack
Quando interagisci con le voci back stack,
il TestNavHostController
ti permette di collegare il controller al tuo
il test LifecycleOwner
, ViewModelStore
e OnBackPressedDispatcher
di
utilizzando le API da cui eredita
NavHostController
Ad esempio, quando esegui il test di un frammento che utilizza un
ViewModel con ambito di navigazione,
devi chiamare
setViewModelStore
:
il 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())
Argomenti correlati
- Creare test delle unità instrumentati - Scopri come configurare la tua suite di test instrumentata ed eseguire test su un Android dispositivo.
- Espresso. Testa l'interfaccia utente della tua app. con Espresso.
- Regole JUnit4 con AndroidX Test: utilizza JUnit 4 con le librerie AndroidX Test per offrire maggiore flessibilità e ridurre il codice boilerplate richiesto nei test.
- Testa i frammenti dell'app:
Scopri come testare i frammenti di app in modo isolato con
FragmentScenario
. - Set up project for AndroidX Test (Configura il progetto per il test AndroidX) - Ulteriori informazioni come dichiarare le librerie necessarie nei file di progetto della tua app per usare AndroidX Testa.