फ़्रैगमेंट नेविगेशन की जांच करना

ऐप्लिकेशन को शिप करने से पहले, उसके नेविगेशन लॉजिक की जांच करना ज़रूरी है. इससे यह पुष्टि की जा सकती है कि आपका ऐप्लिकेशन आपकी उम्मीद के मुताबिक काम कर रहा है.

नेविगेशन कॉम्पोनेंट, डेस्टिनेशन के बीच नेविगेशन मैनेज करने, आर्ग्युमेंट पास करने, और FragmentManager के साथ काम करने से जुड़े सभी काम करता है. इन सुविधाओं की पहले ही अच्छी तरह से जांच की जा चुकी है. इसलिए, आपको अपने ऐप्लिकेशन में इनकी फिर से जांच करने की ज़रूरत नहीं है. हालांकि, यह जांच करना ज़रूरी है कि आपके फ़्रैगमेंट में मौजूद ऐप्लिकेशन के हिसाब से कोड और उनके NavController के बीच इंटरैक्शन कैसा है.

अलग से टेस्ट करना

फ़्रैगमेंट इंटरैक्शन को उनके NavController के साथ अलग से टेस्ट करने के लिए, Navigation 2.3 और इसके बाद के वर्शन में 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 स्क्रीन पर ले जाता है, तो आपको यह पुष्टि करनी होगी कि यह फ़्रैगमेंट, 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 की तरह, TestNavHostController को चालू करने के लिए, आपको setGraph को कॉल करना होगा. इस उदाहरण में, जिस फ़्रैगमेंट की जांच की जा रही है वह हमारे ग्राफ़ का शुरुआती डेस्टिनेशन था. TestNavHostController, setCurrentDestination तरीका उपलब्ध कराता है. इसकी मदद से, मौजूदा डेस्टिनेशन (और चाहें, तो उस डेस्टिनेशन के लिए आर्ग्युमेंट) सेट किया जा सकता है. इससे, आपका टेस्ट शुरू होने से पहले NavController सही स्थिति में होता है.

NavHostFragment के जिस इंस्टेंस का इस्तेमाल NavHostFragment करता है उससे अलग, TestNavHostController, navigate() को कॉल करने पर, navigate() के बुनियादी व्यवहार (जैसे कि FragmentTransaction, जो FragmentNavigator करता है) को ट्रिगर नहीं करता. यह सिर्फ़ TestNavHostController की स्थिति को अपडेट करता है.NavHostController

FragmentScenario की मदद से NavigationUI को टेस्ट करना

ऊपर दिए गए उदाहरण में, titleScenario.onFragment() को दिया गया कॉलबैक तब कॉल किया जाता है, जब फ़्रैगमेंट अपने लाइफ़साइकल के दौरान RESUMED स्थिति में पहुंच जाता है. इस समय तक, फ़्रैगमेंट का व्यू पहले ही बन चुका होता है और अटैच हो चुका होता है. इसलिए, सही तरीके से टेस्ट करने के लिए, लाइफ़साइकल में बहुत देर हो सकती है. उदाहरण के लिए, जब फ़्रैगमेंट में व्यू के साथ NavigationUI का इस्तेमाल किया जाता है, जैसे कि आपके फ़्रैगमेंट से कंट्रोल किए जाने वाले Toolbar के साथ, तब फ़्रैगमेंट के RESUMED स्थिति में पहुंचने से पहले, अपने NavController के साथ सेटअप करने के तरीकों को कॉल किया जा सकता है. इसलिए, आपको लाइफ़साइकल में पहले ही 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;
    }
});

इस तकनीक का इस्तेमाल करने से, onViewCreated() को कॉल करने से पहले NavController उपलब्ध हो जाता है. इससे फ़्रैगमेंट, क्रैश हुए बिना NavigationUI तरीकों का इस्तेमाल कर पाता है.

बैक स्टैक एंट्री के साथ इंटरैक्शन की जांच करना

बैक स्टैक एंट्री के साथ इंटरैक्ट करते समय, TestNavHostController की मदद से कंट्रोलर को अपने टेस्ट LifecycleOwner, ViewModelStore, और OnBackPressedDispatcher से कनेक्ट किया जा सकता है. इसके लिए, NavHostController से इनहेरिट किए गए एपीआई का इस्तेमाल किया जाता है.

उदाहरण के लिए, नेविगेशन स्कोप वाले ViewModel का इस्तेमाल करने वाले फ़्रैगमेंट की जांच करते समय, आपको TestNavHostController पर setViewModelStore को कॉल करना होगा:

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())