iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 26
0
自我挑戰組

Android Architecture 及 Unit Test系列 第 26

[Day 26] Test:Part 8 Navigation Test

  • 分享至 

  • xImage
  •  

今天主要講 Navigation Component 的測試。

本來今天想要完成測試的,結果寫到後來發現需要使用到 FragmentScenario ,但是現在 FragmentScenario 還不能很好的支援 Dagger ,因此今天的程式會以簡單的小程式展現。

如果想知道 FragmentScenario 為何無法支援 Dagger ,可以到 Google Issue Tracker 查看進一步的說明。

Gradle dependency

先在 gradle 加入:

dependency {
    def fragmentVersion = '1.1.0-alpha07'
    implementation "androidx.fragment:fragment-testing:$fragmentVersion"
}

Test Fragment Navigation

其實要測試 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 裡也有測試,也可以去參考看看

使用 FragmentScenario 測試 NavigationUI

在前面的範例中,使用了 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 而不會有問題了。


上一篇
[Day 25] Test:Part 7 Activity
下一篇
[Day 27] Test:Part 9 API Test
系列文
Android Architecture 及 Unit Test30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言