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 antartujuan, 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, 2.3.0-alpha01 Navigasi dan yang lebih tinggi menyediakan TestNavHostController yang memberikan API untuk menyetel tujuan saat ini dan memverifikasi data sebelumnya setelah operasi NavController.navigate().

Anda dapat menambahkan artefak Pengujian Navigasi ke project dengan menambahkan dependensi berikut dalam file build.gradle modul aplikasi Anda:

dependencies {
  def nav_version = "2.3.0"

  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}

Misalnya, Anda membuat game trivia. Game dimulai dengan title_screen menavigasi ke layar in_game saat pengguna mengeklik 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 mengeklik Play, pengujian Anda harus memverifikasi bahwa fragmen ini telah memindahkan NavController ke layar R.id.in_game.

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

Kotlin

@RunWith(AndroidJUnit4::class)
class TitleScreenTest {

    @Test
    fun testNavigationToInGameScreen() {
        // Create a TestNavHostController
        val navController = TestNavHostController(
            ApplicationProvider.getApplicationContext())
        navController.setGraph(R.navigation.trivia)

        // 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(), 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());
        navController.setGraph(R.navigation.triva);

        // 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(), 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);
    }
}

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

Seperti halnya NavController yang sebenarnya, Anda harus memanggil setGraph untuk menginisialisasi TestNavHostController. Dalam contoh ini, fragmen yang diuji adalah tujuan awal dari grafik kami. TestNavHostController menyediakan setCurrentDestination yang memungkinkan Anda menetapkan tujuan saat ini (dan secara opsional, argumen untuk tujuan tersebut) sehingga NavController dalam status yang benar sebelum dimulai pengujian.

Tidak seperti instance NavHostController yang akan digunakan oleh NavHostFragment, TestNavHostController tidak memicu perilaku navigate() yang mendasarinya (seperti FragmentTransaction yang dilakukan FragmentNavigator) saat Anda memanggil navigate() - hanya mengupdate status TestNavHostController.

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 TestNavHostController 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 perlu membuat NavController pada saat onViewCreated() dipanggil. Menggunakan pendekatan onFragment() sebelumnya akan menetapkan TestNavHostController 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 NavController
        fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
            if (viewLifecycleOwner != null) {
                // The fragment’s view has just been created
                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) {
                    Navigation.setViewNavController(titleScreen.requireView(), navController);
                }

            }
        });
        return titleScreen;
    }
});

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

Menguji interaksi dengan entri data sebelumnya

Saat berinteraksi dengan entri data sebelumnya, TestNavHostController memungkinkan Anda menghubungkan pengontrol ke LifecycleOwner, ViewModelStore, dan OnBackPressedDispatcher pengujian Anda sendiri dengan menggunakan API yang diwarisi dari NavHostController.

Misalnya, saat menguji fragmen yang menggunakan ViewModel cakupan navigasi, Anda harus memanggil setViewModelStore di TestNavHostController:

Kotlin

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

// This allows fragments to use by navGraphViewModels()
navController.setViewModelStore(ViewModelStore())

navController.setGraph(R.navigation.trivia)

Java

TestNavHostController nmockNavController = new TestNavHostController(ApplicationProvider.getApplicationContext())mock(NavController.class);

// This allows fragments to use new ViewModelProvider() with a NavBackStackEntry
navController.setViewModelStore(new ViewModelStore())

navController.setGraph(R.navigation.triva);