今天主要講 Navigation Component 的測試。
本來今天想要完成測試的,結果寫到後來發現需要使用到 FragmentScenario ,但是現在 FragmentScenario 還不能很好的支援 Dagger ,因此今天的程式會以簡單的小程式展現。
如果想知道 FragmentScenario 為何無法支援 Dagger ,可以到 Google Issue Tracker 查看進一步的說明。
先在 gradle 加入:
dependency {
def fragmentVersion = '1.1.0-alpha07'
implementation "androidx.fragment:fragment-testing:$fragmentVersion"
}
其實要測試 Fragment Navigation 並不難,既然要測試的是 Fragment 與 Navigation 的交互,那麼首先要做的就是先 Mock 一個假的 NavController
:
@RunWith(AndroidJUnit4::class)
class TasksNavigationTest {
@Test
fun testTitleNavigation() {
// Create a mock NavController
val mockNavController = mockK(NavController::class.java)
}
}
就拿官方的範例舉例,現在有一個 Fragment TitleScreen 點擊一個按鈕到另一個 Fragment
InGameScreen ,TitleScreen 程式如下:
class TitleScreen : Fragment() {
......
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)
}
}
}
要驗證也很簡單,只需要在點擊按鈕後檢查有沒有調用NavController.navigate()
就好:
@RunWith(AndroidJUnit4::class)
class TitleScreenTest {
@Test
fun testNavigationToInGameScreen() {
val mockNavController = mockK(NavController::class.java)
// 建立一個 FragmentScenario
val titleScenario = launchFragmentInContainer<TitleScreen>()
// 在 Fragment 上設置 navigation
titleScenario.onFragment { fragment ->
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
onView(withId(R.id.play_btn)).perform(click())
verify {
mockNavController.navigate(R.id.action_title_screen_to_in_game)
}
}
}
同樣的做法在 TasksFragment 裡也有測試,也可以去參考看看
在前面的範例中,使用了 titleScenario.onFragment
這個 callback 完成測試,但是調用這個方法時 Fragment 的生命週期會切換到 RESUME
狀態。
有時候我們會在 Fragment 建立後處理一些事情,像是在 onViewCreated
後去設置 Toolbar
之類的,很顯然這些操作必須要在達到 RESUME
狀態前完成,因此需要一個在生命週期的前面設置 NavController 的方法。
修改一下 TitleScreen 的內容:
class TitleScreen : Fragment() {
......
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val navController = view.findNavController()
view.findViewById<Toolbar>(R.id.toolbar).setupWithNavController(navController)
}
}
如上面的程式碼所示,需要在 onViewCreated
時就要 mock NavController ,使用 onFragment
mock NavController 太晚,從而導致呼叫 findNavController()
時失敗。
FragmentScenario 有一個 FragmentFactory ,他是一個可以控制 Fragment 實例化的 interface ,透過他再搭配 Fragment.getViewLifecycleOwnerLiveData()
方法就可以控制 onCreateView()
之後的生命週期:
val scenario = launchFragmentInContainer {
TitleScreen().also { fragment ->
fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
if (viewLifecycleOwner != null) {
Navigation.setViewNavController(
fragment.requireView(),
mockNavController
)
}
}
}
}
這樣就可以讓 Fragment 使用 NavigationUI 而不會有問題了。