คุณจำเป็นต้องทดสอบตรรกะการนำทางของแอปก่อนที่จะจัดส่ง เพื่อยืนยันว่าแอปพลิเคชันใช้งานได้ตามที่คุณคาดหวัง
คอมโพเนนต์การนำทางจะจัดการงานทั้งหมดในการจัดการการนำทางระหว่าง
ปลายทาง การส่งอาร์กิวเมนต์ และการทำงานกับ
FragmentManager
ความสามารถเหล่านี้ได้รับการทดสอบที่เข้มงวดแล้ว คุณจึงไม่จำเป็นต้องทดสอบ
อีกครั้งในแอปของคุณ แต่สิ่งสำคัญในการทดสอบคือการโต้ตอบ
ระหว่างโค้ดเฉพาะแอปใน Fragment ของคุณและของ
NavController
คู่มือนี้จะอธิบายสถานการณ์การนําทางที่พบบ่อยบางส่วนและวิธีทดสอบการใช้งาน
ทดสอบการนำทาง Fragment
หากต้องการทดสอบการโต้ตอบส่วนย่อยด้วย NavController
แบบแยกเดี่ยว
การนำทาง 2.3 ขึ้นไปมอบ
TestNavHostController
ที่มี API สำหรับการตั้งค่าปลายทางปัจจุบันและยืนยันด้านหลัง
ซ้อนหลัง
NavController.navigate()
คุณสามารถเพิ่มอาร์ติแฟกต์การทดสอบการนำทางลงในโครงการของคุณได้โดยการเพิ่ม
ทรัพยากร Dependency ต่อไปนี้ในไฟล์ build.gradle
ของโมดูลแอป
Groovy
dependencies { def nav_version = "2.8.4" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.4" 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 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); } }
ตัวอย่างด้านบนจะสร้างอินสแตนซ์ของ TestNavHostController
และกำหนดอินสแตนซ์
ลงในส่วนย่อย จากนั้นใช้ Espresso ในการขับเคลื่อน UI และยืนยันว่า
การนำทางที่เหมาะสม
เช่นเดียวกับ NavController
จริง คุณต้องเรียกใช้ setGraph
เพื่อเริ่มต้น
TestNavHostController
ในตัวอย่างนี้ ส่วนย่อยที่ทดสอบคือ
ปลายทางเริ่มต้นของกราฟ TestNavHostController
มอบ
setCurrentDestination
ที่ช่วยให้คุณตั้งค่าปลายทางปัจจุบัน (และ
อาร์กิวเมนต์สำหรับปลายทางนั้น) เพื่อให้ NavController
อยู่ใน
สถานะที่ถูกต้องก่อนที่การทดสอบจะเริ่มต้น
ซึ่งแตกต่างจากอินสแตนซ์ NavHostController
ที่ NavHostFragment
จะใช้
TestNavHostController
จะไม่ทริกเกอร์ navigate()
ที่เกี่ยวข้อง
พฤติกรรม (เช่น FragmentTransaction
ที่ FragmentNavigator
ทำ)
เมื่อคุณเรียกใช้ navigate()
ระบบจะอัปเดตสถานะของ
TestNavHostController
ทดสอบ NavigationUI ด้วย Fragmentสถานการณ์
ในตัวอย่างก่อนหน้านี้ Callback ที่ให้ไว้กับ titleScenario.onFragment()
จะมีการเรียกหลังจากที่ส่วนย่อยได้เคลื่อนที่ผ่านวงจรไปยัง
RESUMED
ถึงตอนนี้ ระบบได้สร้างมุมมองของส่วนย่อยและแนบไว้แล้ว
จึงอาจสายเกินไปที่จะทดสอบอย่างถูกต้อง เช่น เมื่อใช้
NavigationUI
ที่มีการดูในส่วนย่อยของคุณ เช่น ด้วย Toolbar
ที่ควบคุม
ตามส่วนย่อยของคุณ คุณสามารถเรียกใช้วิธีการตั้งค่าด้วย NavController
ได้ก่อนวันที่
ส่วนย่อยถึงสถานะ RESUMED
คุณจึงต้องหาวิธีตั้งค่า
เร็วขึ้น 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
อินเทอร์เฟซที่ใช้ลงทะเบียน Callback สำหรับเหตุการณ์ในวงจรได้ วิธีนี้
รวมกับ Fragment.getViewLifecycleOwnerLiveData()
เพื่อรับ
Callback ที่ตามหลัง 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; } });
เมื่อใช้เทคนิคนี้ NavController
จะพร้อมให้ใช้งานก่อน
มีการเรียก onViewCreated()
ทำให้ส่วนย่อยสามารถใช้เมธอด NavigationUI
ได้
โดยไม่ขัดข้อง
การทดสอบการโต้ตอบกับรายการ Back Stack
เมื่อโต้ตอบกับรายการแบ็กสแต็ก
TestNavHostController
ช่วยให้คุณเชื่อมต่อตัวควบคุมเข้ากับ
ทดสอบ LifecycleOwner
, ViewModelStore
และ OnBackPressedDispatcher
โดย
โดยใช้ API ที่รับค่าเดิมมา
NavHostController
ตัวอย่างเช่น เมื่อทดสอบส่วนย่อยที่ใช้องค์ประกอบ
ViewModel ที่กำหนดขอบเขตการนำทาง
คุณต้องโทร
setViewModelStore
ในวัน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())
หัวข้อที่เกี่ยวข้อง
- สร้างการทดสอบ 1 หน่วยแบบมีเครื่องควบคุม - ดูวิธีตั้งค่าชุดทดสอบแบบมีเครื่องวัดและทำการทดสอบใน Android อุปกรณ์
- Espresso - ทดสอบ UI ของแอป ด้วยเอสเปรสโซ
- กฎ JUnit4 กับการทดสอบ AndroidX - ใช้ JUnit 4 ร่วมกับไลบรารี AndroidX Test เพื่อเพิ่มความยืดหยุ่นและลด โค้ด Boilerplate ที่ต้องใช้ในการทดสอบ
- ทดสอบส่วนย่อยของแอป -
ดูวิธีทดสอบส่วนย่อยของแอปแยกต่างหากด้วย
FragmentScenario
- ตั้งค่าโปรเจ็กต์สำหรับการทดสอบ AndroidX - ดูข้อมูลเพิ่มเติม วิธีประกาศไลบรารีที่จำเป็นในไฟล์โปรเจ็กต์ของแอปเพื่อใช้ AndroidX ทดสอบ