חשוב לבדוק את לוגיקת הניווט של האפליקציה לפני המשלוח כדי לוודא שהאפליקציה פועלת כצפוי.
רכיב הניווט מטפל בכל העבודה של ניהול הניווט בין
יעדים, העברת ארגומנטים ועבודה עם
FragmentManager
היכולות האלה כבר נבדקו בקפדנות, לכן לא צריך לבצע בדיקות
אותם שוב באפליקציה. עם זאת, מה שחשוב לבדוק הן את האינטראקציות
בין הקוד הספציפי לאפליקציה במקטעים שלך
NavController
במדריך הזה מפורטים תרחישי ניווט נפוצים ונסביר איך לבדוק אותם.
בדיקת הניווט במקטעים
כדי לבדוק אינטראקציות עם מקטעים עם NavController
בנפרד,
ניווט בגרסה 2.3 ואילך מספק
TestNavHostController
שמספק ממשקי API להגדרת היעד הנוכחי ולאימות
סטאק אחרי
NavController.navigate()
ב-AI.
כדי להוסיף לפרויקט ארטיפקט של בדיקת הניווט, צריך להוסיף את
התלות הבאה בקובץ build.gradle
של מודול האפליקציה:
Groovy
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") }
נניח שאתם מפתחים משחק טריוויה. המשחק מתחיל title_screen ועוברים למסך in_game כשהמשתמש לוחץ לשחק.
המקטע שמייצג את title_screen עשוי להיראות כך:
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); }); } }
כדי לבדוק שהאפליקציה מנווטת באופן תקין את המשתמש למסך in_game
המשתמש לוחץ על הפעלה, הבדיקה צריכה לאמת שהמקטע הזה
מעביר את NavController
בצורה נכונה למסך R.id.in_game
.
באמצעות שילוב של FragmentScenario
, Espresso,
ו-TestNavHostController
, אפשר ליצור מחדש את התנאים הדרושים
בתרחיש הזה, כפי שאפשר לראות בדוגמה הבאה:
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); } }
בדוגמה שלמעלה, נוצר מופע של TestNavHostController
ומקצה אותו
למקטע. לאחר מכן היא משתמשת ב-Espresso כדי להפעיל את ממשק המשתמש ומאמתת
ננקטת פעולת ניווט מתאימה.
בדיוק כמו NavController
אמיתי, צריך לקרוא ל-setGraph
כדי לאתחל
TestNavHostController
. בדוגמה זו, המקטע שנבדק
היעד ההתחלתי של התרשים. TestNavHostController
מספק
setCurrentDestination
שמאפשרת להגדיר את היעד הנוכחי (ואופציונלית,
ארגומנטים עבור יעד זה) כך שה-NavController
נמצא
לפני שהבדיקה מתחילה.
בניגוד למופע של NavHostController
שבו משתמש NavHostFragment
,
TestNavHostController
לא מפעיל את ה-navigate()
הבסיסי
התנהגות (כמו FragmentTransaction
ש-FragmentNavigator
עושה)
כשקוראים לפונקציה navigate()
, המערכת מעדכנת רק את המצב של
TestNavHostController
.
בדיקת ממשק הניווט של המשתמש עם FragmentScenario
בדוגמה הקודמת, הקריאה החוזרת (callback) סופקה ל-titleScenario.onFragment()
נקראת אחרי שהמקטע עבר במחזור החיים שלו אל
RESUMED
. בשלב הזה, תצוגת המקטע כבר נוצרה וצורפה.
ולכן ייתכן שיהיה מאוחר מדי במחזור החיים כדי לבדוק כראוי. לדוגמה, כשמשתמשים
NavigationUI
עם צפיות במקטע שלך, למשל עם Toolbar
מבוקרת
לפי המקטע שלך, ניתן לקרוא לשיטות הגדרה באמצעות NavController
לפני
המקטע מגיע למצב RESUMED
. לכן צריך דרך להגדיר
TestNavHostController
בשלב מוקדם יותר במחזור החיים.
ניתן לכתוב מקטע שהוא הבעלים של Toolbar
משלו באופן הבא:
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); } }
כאן אנחנו צריכים את ה-NavController
שנוצר עד למועד הקריאה של onViewCreated()
.
אם תשתמש בגישה הקודמת של onFragment()
, המערכת תגדיר את TestNavHostController
בשלב מאוחר מדי במחזור החיים, מה שגורם לקריאה של findNavController()
להיכשל.
FragmentScenario
מציע
FragmentFactory
ממשק שבו אפשר להשתמש כדי לרשום קריאות חוזרות (callback) לאירועים במחזור חיים. מי יכול
ישולבו עם Fragment.getViewLifecycleOwnerLiveData()
כדי לקבל
קריאה חוזרת (callback) שמיד לאחר onCreateView()
, כפי שמוצג למטה
דוגמה:
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; } });
באמצעות השיטה הזו, NavController
זמין לפני
הפונקציה onViewCreated()
נקראת, כך שהמקטע יכול להשתמש ב-methods NavigationUI
בלי לקרוס.
בדיקת אינטראקציות עם רשומות של מקבץ אחורי
באינטראקציה עם הרשומות של המקבץ 'הקודם':
באמצעות TestNavHostController
אפשר לחבר את הבקר
בדיקה של LifecycleOwner
, ViewModelStore
ו-OnBackPressedDispatcher
באמצעות
באמצעות ממשקי ה-API שהיא מקבלת בירושה
NavHostController
.
לדוגמה, בעת בדיקת מקטע שמשתמש ב-
היקף התצוגה של ViewModel,
צריך להתקשר
setViewModelStore
ב-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())
נושאים קשורים
- בניית בדיקות יחידה אינסטרומנטלית – איך מגדירים את חבילת הבדיקות האינסטרומנטטיביות ומריצים בדיקות ב-Android במכשיר.
- Espresso - בדיקת ממשק המשתמש של האפליקציה עם 'אספרסו'.
- כללי JUnit4 עם בדיקת AndroidX – שימוש ב-JUnit4 שונים כללים עם ספריות הבדיקה של AndroidX כדי לספק יותר גמישות ולהפחית את הקוד הסטנדרטי שנדרש בבדיקות.
- בדיקת מקטעי האפליקציה -
איך בודקים מקטעי אפליקציות בנפרד באמצעות
FragmentScenario
- הגדרת פרויקט לבדיקת AndroidX - למידה איך מצהירים על הספריות הנדרשות בקובצי הפרויקט של האפליקציה לצורך שימוש ב-AndroidX בדיקה.