חשוב לבדוק את לוגיקת הניווט של האפליקציה לפני המשלוח כדי לוודא שהאפליקציה פועלת כצפוי.
רכיב הניווט מטפל בכל העבודה של ניהול הניווט בין
יעדים, העברת ארגומנטים ועבודה עם
FragmentManager
היכולות האלה כבר נבדקו בקפדנות, לכן לא צריך לבצע בדיקות
אותם שוב באפליקציה. עם זאת, מה שחשוב לבדוק הן את האינטראקציות
בין הקוד הספציפי לאפליקציה במקטעים שלך
NavController
במדריך הזה מפורטים תרחישי ניווט נפוצים ונסביר איך לבדוק אותם.
בדיקת הניווט במקטעים
כדי לבדוק אינטראקציות עם מקטעים עם NavController
בנפרד,
ניווט בגרסה 2.3 ואילך מספק
TestNavHostController
שמספק ממשקי API להגדרת היעד הנוכחי ולאימות
סטאק אחרי
NavController.navigate()
ב-AI.
כדי להוסיף לפרויקט ארטיפקט של בדיקת הניווט, צריך להוסיף את
התלות הבאה בקובץ build.gradle
של מודול האפליקציה:
dependencies { def nav_version = "2.8.6" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
dependencies { val nav_version = "2.8.6" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
נניח שאתם מפתחים משחק טריוויה. המשחק מתחיל title_screen ועוברים למסך in_game כשהמשתמש לוחץ לשחק.
המקטע שמייצג את title_screen עשוי להיראות כך:
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)
}
}
}
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
, אפשר ליצור מחדש את התנאים הדרושים
בתרחיש הזה, כפי שאפשר לראות בדוגמה הבאה:
@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)
}
}
@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
משלו באופן הבא:
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)
}
}
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()
, כפי שמוצג למטה
דוגמה:
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)
}
}
}
}
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
:
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
// This allows fragments to use by navGraphViewModels()
navController.setViewModelStore(ViewModelStore())
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 בדיקה.