Navigation componentをテストする

Navigation 2.3.0-alpha01からナビゲーションをテストするためのAPIが用意されたので使ってみます。
（2020年2月29日現在、最新バージョンは2.3.0-alpha02です。この記事も2.3.0-alpha02に基づいています。）

#従来のNavigationテスト
今までNavigationのテストをする場合、以下のようなテストコードでした。

```TitleScreenTest.kt
@RunWith(AndroidJUnit4::class)
class TitleScreenTest {

    @Test
    fun testNavigationToInGameScreen() {
        // Create a mock NavController
        val mockNavController = mock(NavController::class.java)

        // Create a graphical FragmentScenario for the TitleScreen
        val titleScenario = launchFragmentInContainer<TitleScreen>()

        // Set the NavController property on the fragment
        titleScenario.onFragment { fragment ->
            Navigation.setViewNavController(fragment.requireView(), mockNavController)
        }

        // Verify that performing a click prompts the correct Navigation action
        onView(ViewMatchers.withId(R.id.play_btn)).perform(ViewActions.click())
        verify(mockNavController).navigate(R.id.action_title_screen_to_in_game)
    }
}
```

何をテストしているかというと、`R.id.play_btn`というViewがクリックされたときに、`navigate(R.id.action_title_screen_to_in_game`が呼び出されるかどうか、というのを検証しています。
ただ、呼び出されたからといってActionが望んだ動作をしているのかということまでは保証できません。
例えば、遷移する際にBack stackをpopする場合やargumentを渡す場合などもテストできたら良さそうです。


#TestNavHostController
Navigation 2.3.0-alpha01から導入されたのが、[`TestNavHostController`](https://developer.android.com/reference/kotlin/androidx/navigation/testing/TestNavHostController)です。
https://developer.android.com/reference/kotlin/androidx/navigation/testing/TestNavHostController

前までは`NavController`をモックしていましたが、`TestNavHostController`を使うことで`NavController.navigate()`後のCurrent destinationやBack stackにアクセスできるようになります。

```TitleScreenTest.kt
@RunWith(AndroidJUnit4::class)
class TitleScreenTest {

    @Test
    fun testNavigationToInGameScreen() {
        // Create a TestNavHostController
        val navController = TestNavHostController(
            ApplicationProvider.getApplicationContext())
        navController.setGraph(R.navigation.trivia)

        // Create a graphical FragmentScenario for the TitleScreen
        val titleScenario = launchFragmentInContainer<TitleScreen>()

        // Set the NavController property on the fragment
        titleScenario.onFragment { fragment ->
            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)
    }
}
```


注目するところは、`assertThat(navController.currentDestination?.id).isEqualTo(R.id.in_game)`という記述。
viewをクリックした後のCurrent destionationのidが、想定しているdestination (Fragment)のidと一致しているか、という検証をしています。
メソッドが呼び出されたかというテストより信頼できます。

##Start destinationではないFragmentでのテストを書きたい場合
`TestNavHostController.setCurrentDestination()`というメソッドが用意されています。
idをセットすることで指定したdestinationからテストを開始することができます。

```StartDestination.kt
val navController = TestNavHostController(
                ApplicationProvider.getApplicationContext()
).apply {
    setGraph(R.navigation.navigation)
    setCurrentDesination(R.id.destination)
}
```

##argumentをテストしたい場合
`NavBackStackEntry.arguments`で取得できます。
SafeArgsを利用している場合も同様で、argumentのkeyはnavigation.xmlで指定しているargumentの"name"になります。

```Args.kt
val argument = navController.currentBackStackEntry.arguments!!["key"]
assertThat(argument).isEqualTo("hoge")
```

##Back stackをテストしたい場合
`TestNavHostController.backStack`ですべてのback stack (`List<NavBackStackEntry>`)が取得できます。
もしくは、`NavController.previeousBackStackEntry`で現在の一つ前にあるstackを取り出すことができます。

```Stack.kt
val backStack = navController.backstack
val previous = navController.previousBackStackEntry

assertThat(previous!!.destination.id).isEqualTo(R.id.destination)
```

#参考
https://developer.android.com/guide/navigation/navigation-testing
https://github.com/android/architecture-components-samples/tree/master/NavigationBasicSample