Fragment全体のユニットテストを書こうとするとAndroid公式のやり方そのままやっても動かなかったりRobolectricが必要になったりして複雑になりがちです。またユニットテストだと実機やエミュレータの画面を利用しないテストとなるので画面を見ずにユニットテストを書くことになり難易度も高いです。

https://developer.android.com/guide/fragments/test?hl=ja

AndroidViewではなくJetpackComposeを使って実装すると比較的簡単にユニットテストが書けるようになります。

https://developer.android.com/jetpack/compose/testing?hl=ja

例として下記のようなNavigationRailのComposeがあるとします。

sealed class NavigationItem(val index: Int, @StringRes val labelRes: Int, @DrawableRes val iconRes: Int) {
    object Rule : NavigationItem(0, R.string.rule_label, R.drawable.ic_rule)
    object AppSetting : NavigationItem(1, R.string.app_setting_label, R.drawable.ic_app_setting)
    companion object {
        val items = listOf(Rule, AppSetting)
    }
}

@Composable
fun NavigationRail(uiState: MainUiState, onClick: (index: Int, item: NavigationItem) -> Unit) {
    NavigationRail {
        NavigationItem.items.forEachIndexed { index, item ->
            NavigationRailItem(
                icon = { Icon(painterResource(item.iconRes), stringResource(item.labelRes)) },
                label = { Text(stringResource(item.labelRes)) },
                selected = uiState.navigationItemIndex == index,
                onClick = { onClick.invoke(index, item) }
            )
        }
    }
}

選択されているアイテムはMainUiStateのnavigationItemIndexで保存しています。

Android公式サイトにはandroidTestImplementation(“androidx.compose.ui:ui-test-junit4:$compose_version”)と書いてありますがtestImplementation(androidx.compose.ui:ui-test-junit4:$compose_version”)とbuild.gradle.ktsに書くことでユニットテストでcreateComposeRule()が利用できます。

ユニットテストを書く前にJetpackCompose用の抽象クラスを用意しておきます。

import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule

abstract class AbstractComposeRule {
    @get:Rule
    val composeRule = createComposeRule()
}

抽象クラスを継承してユニットテストを書きます。

import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import [your_package].viewmodel.MainUiState
import [your_package].AbstractComposeRule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class NavigationRailSpec : AbstractComposeRule() {

    @Test
    fun click_rules() {
        composeRule.setContent {
            NavigationRail(MainUiState()) { index, item ->
                val expectedItem = NavigationItem.Rule
                assertThat(index).isEqualTo(expectedItem.index)
                assertThat(item).isEqualTo(expectedItem)
            }
        }
        composeRule.onNodeWithText("Rules", useUnmergedTree = true).performClick()
    }

    @Test
    fun click_settings() {
        composeRule.setContent {
            NavigationRail(MainUiState()) { index, item ->
                val expectedItem = NavigationItem.AppSetting
                assertThat(index).isEqualTo(expectedItem.index)
                assertThat(item).isEqualTo(expectedItem)
            }
        }
        composeRule.onNodeWithText("Settings", useUnmergedTree = true).performClick()
    }
}

NavigationRail上の”Rules”または”Settings”という文字列をクリックするとコールバックが実行され、渡された値をアサーションするという流れです。コールバックでViewModelの特定の関数が実行されたか確認したいなどもできると思います。

Fragmentのユニットテストを書くより簡単で高速に実行できるのでComposeのユニットテストを積極的に書いていきましょう。