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,
Navigasi 2.3 dan yang lebih baru 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:
Groovy
dependencies { def nav_version = "2.8.5" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.5" androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") }
Misalnya, Anda membuat game trivia. Game dimulai dengan title_screen 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 via 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 public void 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 via 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 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()
- ini hanya memperbarui 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 untuk diuji dengan benar dalam siklus proses. 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(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
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 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 instrumentasi - Pelajari cara menyiapkan rangkaian pengujian instrumentasi dan menjalankan pengujian pada perangkat 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.