من المهم اختبار منطق التنقّل في تطبيقك قبل إطلاقه للتأكّد من أنّه يعمل على النحو المتوقّع.
يتولّى مكوّن Navigation جميع مهام إدارة التنقّل بين
الوجهات وتمرير الوسيطات والتعامل مع
FragmentManager. تم اختبار هذه الإمكانات بدقة من قبل، لذا ما مِن حاجة إلى اختبارها مرة أخرى في تطبيقك. ومع ذلك، من المهم اختبار التفاعلات بين الرمز البرمجي الخاص بالتطبيق في أجزائك وNavController.
الاختبار بشكل منفصل
لاختبار تفاعلات الأجزاء مع NavController بشكل منفصل،
يوفّر الإصدار 2.3 من Navigation والإصدارات الأحدث
TestNavHostController الذي يوفّر واجهات برمجة تطبيقات لـ
ضبط الوجهة الحالية والتحقّق من سجلّ الرجوع بعد
NavController.navigate() عمليات.
يمكنك إضافة عنصر Navigation Testing إلى مشروعك من خلال إضافة التبعية التالية في ملف build.gradle الخاص بوحدة تطبيقك:
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") }
لنفترض أنّ لديك لعبة معلومات عامة. تبدأ اللعبة بشاشة 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 عندما
ينقر المستخدم على Play، يجب أن يتحقّق اختبارك من أنّ هذا الجزء
ينقل 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 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);
}
}
ينشئ المثال السابق مثيلاً من TestNavHostController ويخصّصه للجزء. بعد ذلك، يستخدم Espresso لتشغيل واجهة المستخدم ويتحقّق من اتّخاذ إجراء التنقّل المناسب.
تمامًا مثل NavController حقيقي، عليك استدعاء setGraph لتهيئة TestNavHostController. في هذا المثال، كان الجزء الذي يتم اختباره هو وجهة البداية في الرسم البياني. TestNavHostController يوفّر طريقة
setCurrentDestination تتيح لك ضبط الوجهة الحالية (ويمكنك اختياريًا ضبط وسيطات لهذه
الوجهة) حتى يكون NavController في الحالة الصحيحة قبل بدء
الاختبار.
على عكس مثيل NavHostController الذي يستخدمه NavHostFragment، TestNavHostController لا يؤدي إلى تفعيل سلوك navigate() الأساسي (مثل FragmentTransaction الذي يفعّله FragmentNavigator) عند استدعاء navigate()، بل يؤدي فقط إلى تعديل حالة TestNavHostController.
اختبار NavigationUI باستخدام FragmentScenario
في المثال السابق، يتم استدعاء رد الاتصال المقدَّم إلى
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
يمكن استخدامها لتسجيل عمليات رد الاتصال لأحداث دورة الحياة. يمكن دمج ذلك مع Fragment.getViewLifecycleOwnerLiveData() لتلقّي رد اتصال يتبع 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()، ما يسمح للجزء باستخدام طرق NavigationUI بدون حدوث عطل.
اختبار التفاعلات مع إدخالات سجلّ الرجوع
عند التفاعل مع إدخالات الأنشطة السابقة، يتيح لك
TestNavHostController ربط وحدة التحكّم بـ
الاختبار الخاص بكLifecycleOwner وViewModelStore وOnBackPressedDispatcher باستخدام
واجهات برمجة التطبيقات التي يرثها من 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: اختبِر واجهة مستخدم تطبيقك باستخدام Espresso.
- قواعد JUnit4 مع AndroidX Test: استخدِم قواعد JUnit 4 مع مكتبات AndroidX Test لتوفير المزيد من المرونة وتقليل رمز النص النموذجي المطلوب في الاختبارات.
- اختبار أجزاء تطبيقك: تعرَّف على كيفية
اختبار أجزاء تطبيقك بشكل منفصل باستخدام
FragmentScenario. - إعداد المشروع لاستخدام AndroidX Test: تعرَّف على كيفية الإعلان عن المكتبات المطلوبة في ملفات مشروع تطبيقك لاستخدام AndroidX Test.