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. Namun, yang perlu diuji adalah interaksi antara kode khusus aplikasi dalam fragmen Anda dan NavController-nya.
Pengujian secara terpisah
Untuk menguji interaksi fragmen dengan NavController-nya secara terpisah,
Navigation 2.3 dan yang lebih tinggi menyediakan
TestNavHostController yang menyediakan 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:
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") }
Pertimbangkan 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 mengklik 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())
// 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);
}
}
Contoh sebelumnya 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 metode
setCurrentDestination yang memungkinkan Anda menetapkan tujuan saat ini (dan secara opsional, argumen untuk tujuan tersebut) sehingga NavController dalam status yang benar sebelum pengujian dimulai.
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 memperbarui status
TestNavHostController.
Menguji NavigationUI dengan FragmentScenario
Pada contoh sebelumnya, callback yang diberikan ke
titleScenario.onFragment() dipanggil setelah fragmen dipindahkan melalui
siklus prosesnya ke status RESUMED. Pada tahap ini, tampilan fragmen telah dibuat dan dilampirkan, sehingga mungkin sudah terlambat untuk diuji dengan benar dalam siklus proses. Misalnya, saat menggunakan NavigationUI dengan
tampilan dalam fragmen Anda, seperti dengan Toolbar yang dikontrol oleh fragmen Anda,
Anda dapat memanggil metode penyiapan dengan NavController sebelum fragmen
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(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);
}
}
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 menawarkan 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
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;
}
});
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())
Java
TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());
// This allows fragments to use new ViewModelProvider() with a NavBackStackEntry
navController.setViewModelStore(new ViewModelStore())
Topik terkait
- Membuat pengujian unit berinstrumen - Pelajari cara menyiapkan rangkaian pengujian berinstrumen dan menjalankan pengujian pada perangkat berteknologi Android.
- Espresso - Uji UI aplikasi Anda dengan Espresso.
- Aturan JUnit4 dengan AndroidX Test - Gunakan aturan JUnit 4 dengan library AndroidX Test untuk memberikan lebih banyak fleksibilitas dan mengurangi kode boilerplate yang diperlukan dalam pengujian.
- Menguji fragmen aplikasi Anda - Pelajari cara
menguji fragmen aplikasi Anda secara terpisah dengan
FragmentScenario. - Menyiapkan project untuk AndroidX Test - Pelajari cara mendeklarasikan library yang dibutuhkan dalam file project aplikasi Anda untuk menggunakan AndroidX Test.