Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Menguji Navigasi

Sebelum mengirimkan logika navigasi aplikasi, Anda harus mengujinya terlebih dahulu agar aplikasi dapat berfungsi seperti yang diharapkan.

Komponen Navigasi menangani semua tugas mengelola navigasi antar-tujuan, meneruskan argumen, dan bekerja dengan FragmentManager. Kemampuan ini sudah diuji secara ketat, sehingga Anda tidak perlu mengujinya kembali. Tetapi, yang perlu untuk diuji adalah interaksi antara kode khusus aplikasi dalam fragmen Anda dan NavController-nya. Panduan ini membahas beberapa skenario navigasi umum beserta cara mengujinya.

Menguji Navigasi fragmen

Untuk menguji interaksi fragmen dengan NavController-nya secara terpisah, Anda dapat memberikan tiruan di dalam pengujian menggunakan Mockito. Kemudian, Anda dapat menggunakan implementasi tiruan tersebut untuk memverifikasi interaksi dengannya.

Misalnya, Anda membuat game trivia. Game dimulai dengan title_screen dan menavigasi ke layar in_game saat pengguna mengklik play.

Fragmen yang mewakili title_screen mungkin akan terlihat seperti berikut:

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);
            });
        }
    }
    

Untuk menguji apakah aplikasi menavigasi pengguna dengan benar ke layar in_game saat pengguna mengklikPlay, pengujian Anda harus memverifikasi bahwa fragmen ini memanggil NavController.navigate() dengan tindakan R.id.action_title_screen_to_in_game.

Dengan menggunakan kombinasi FragmentScenario, Espresso, dan Mockito, Anda dapat membuat ulang kondisi yang diperlukan untuk menguji skenario ini, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

    @RunWith(AndroidJUnit4::class)
    class TitleScreenTest {

        @Test
        fun testNavigationToInGameScreen() {
            // Create a mock NavController
            val mockNavController = mock(NavController::class.java)

            // Create a graphical FragmentScenario for the TitleScreen
            val titleScenario = launchFragmentInContainer<TitleScreen>()

            // Set the NavController property on the fragment
            titleScenario.onFragment { fragment ->
                Navigation.setViewNavController(fragment.requireView(), mockNavController)
            }

            // Verify that performing a click prompts the correct Navigation action
            onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click())
            verify(mockNavController).navigate(R.id.action_title_screen_to_in_game)
        }
    }
    

Java

    @RunWith(AndroidJUnit4.class)
    public class TitleScreenTestJava {

        @Test
        public void testNavigationToInGameScreen() {

            // Create a mock NavController
            NavController mockNavController = mock(NavController.class);

            // Create a graphical FragmentScenario for the TitleScreen
            FragmentScenario<TitleScreen> titleScenario = FragmentScenario.launchInContainer(TitleScreen.class);

            // Set the NavController property on the fragment
            titleScenario.onFragment(fragment ->
                    Navigation.setViewNavController(fragment.requireView(), mockNavController)
            );

            // Verify that performing a click prompts the correct Navigation action
            onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click());
            verify(mockNavController).navigate(R.id.action_title_screen_to_in_game);
        }
    }
    

Contoh di atas membuat instance NavController tiruan dan menetapkannya ke fragmen. Kemudian, Espresso akan digunakan untuk menjalankan UI dan memverifikasi bahwa tindakan navigasi yang sesuai telah dilakukan.

Menguji NavigationUI dengan FragmentScenario

Pada contoh sebelumnya, callback yang diberikan ke titleScenario.onFragment() akan dipanggil setelah fragmen dipindahkan melalui siklus prosesnya ke status RESUMED. Pada tahap ini, tampilan fragmen telah dibuat dan dilampirkan, sehingga mungkin sudah terlambat dalam siklus proses untuk diuji dengan benar. Misalnya, saat menggunakan NavigationUI dengan tampilan dalam fragmen Anda, seperti dengan Toolbar yang dikontrol oleh fragmen, Anda dapat memanggil metode penyiapan dengan NavController sebelum fragmen tersebut mencapai status RESUMED. Dengan demikian, Anda membutuhkan cara untuk menetapkan NavController tiruan lebih awal dalam siklus proses.

Fragmen yang memiliki Toolbar-nya sendiri dapat ditulis sebagai berikut:

Kotlin

    class TitleScreen : Fragment() {

        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ) = return inflater.inflate(R.layout.fragment_title_screen, container, false)

        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 {

        @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) {
            NavController navController = Navigation.findNavController(view);
            view.findViewById(R.id.toolbar).setupWithNavController(navController);
        }
    }
    

Dalam hal ini, kita membutuhkan tiruan NavController yang dibuat saat onViewCreated() dipanggil. Menggunakan pendekatan onFragment() sebelumnya akan menetapkan NavController tiruan terlalu lambat pada siklus proses, yang menyebabkan panggilan findNavController() gagal.

FragmentScenario memberikan antarmuka FragmentFactory yang dapat digunakan untuk mendaftarkan callback untuk peristiwa siklus proses. Hal ini dapat dikombinasikan dengan Fragment.getViewLifecycleOwnerLiveData() untuk menerima callback yang segera mengikuti onCreateView(), seperti yang ditunjukkan pada contoh berikut:

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 mock NavController
            fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                if (viewLifecycleOwner != null) {
                    // The fragment’s view has just been created
                    Navigation.setViewNavController(fragment.requireView(), mockNavController)
                }
            }
        }
    }
    

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 mock NavController
            titleScreen.getViewLifecycleOwnerLiveData().observeForever(new Observer<LifecycleOwner>() {
                @Override
                public void onChanged(LifecycleOwner viewLifecycleOwner) {

                    // The fragment’s view has just been created
                    if (viewLifecycleOwner != null) {
                        Navigation.setViewNavController(titleScreen.requireView(), mockNavController);
                    }

                }
            });
            return titleScreen;
        }
    });
    

Dengan menggunakan teknik ini, NavController akan tersedia sebelum onViewCreated() dipanggil, yang memungkinkan fragmen untuk menggunakan metode NavigationUI tanpa error.