第15回:ComposeでテストしやすいUIを書く方法

社員ブログ

※ChatGPTを使用して記事を作成しています。

Jetpack Composeを導入したチームで、よくこんな会話を聞きます。

「ComposeってUIテスト難しくない?」
「テスト書こうとするとViewModelが必要になるんだよね…」

しかし実際は逆です。

ComposeはUIテストが非常に書きやすいUIフレームワークです。

ただし条件があります。

UIの設計が正しくできている場合だけです。

今回は
ComposeでテストしやすいUIを書くための設計
について整理します。

なぜUIテストが難しくなるのか

まずは、テストしにくいComposableを見てみましょう。

@Composable
fun UserProfile(viewModel: UserViewModel = viewModel()) {
    val user by viewModel.user.collectAsState()
    Column {
        Text(user.name)
        Button(
            onClick = {
                viewModel.followUser()
            }
        ) {
            Text("Follow")
        }
    }
}

このコードには、UIテストを難しくする要因が3つあります。

問題① ViewModel依存

viewModel: UserViewModel

テスト時に

  • Fake ViewModel
  • Mock ViewModel

などを用意する必要があります。

問題② State取得がUI内部

viewModel.user.collectAsState()

UIが 状態取得の責務 を持っています。

問題③ ロジック依存

viewModel.followUser()

UIが直接ロジックを呼び出しています。

つまりこのUIは

UI + State + Logic

が混ざっています。

この構造ではテストが難しくなります。

テストしやすいUIの基本構造

ComposeでテストしやすいUIは、次の形になります。

Composable(
state,
event
)

つまり

  • 状態は引数
  • イベントはコールバック

です。

改善したUI

先ほどのコードを改善します。

@Composable
fun UserProfile(
    userName: String,
    onFollowClick: () -> Unit
) {    Column {
        Text(userName)
        Button(
            onClick = onFollowClick
        ) {
            Text("Follow")
        }
    }
}

これで何が変わるでしょうか。

このUIは

  • ViewModel不要
  • Repository不要
  • API不要

になります。

つまり

UI単体でテスト可能 になります。

Compose UIテストの書き方

ComposeのUIテストは非常にシンプルです。

@get:Rule
val composeTestRule = createComposeRule()

テスト対象をセットします。

composeTestRule.setContent {
    UserProfile(
        userName = "Taro",
        onFollowClick = {}
    )
}

そしてUIを検証します。

composeTestRule
.onNodeWithText("Taro")
.assertExists()

これだけです。

ボタンイベントのテスト

イベントのテストも簡単です。

@Test
fun followButtonClick() {
    var clicked = false
    composeTestRule.setContent {
        UserProfile(
            userName = "Taro",
            onFollowClick = {
                clicked = true
            }
        )
    }
    composeTestRule
        .onNodeWithText("Follow")
        .performClick()
    assert(clicked)
}

ポイントはシンプルです。

UIはイベントを通知するだけ

なのでテストもしやすくなります。

UI Stateを使うとさらにテストしやすい

実務ではStateをまとめて渡すことが多いです。

data class UserProfileUiState(
val name: String,
val isFollowing: Boolean
)

ComposableはStateを受け取ります。

@Composable
fun UserProfile(
    state: UserProfileUiState,
    onFollowClick: () -> Unit
) {
    Column {
        Text(state.name)
        Button(
            onClick = onFollowClick
        ) {
            Text(
                if (state.isFollowing)
                    "Following"
                else
                    "Follow"
            )
        }
    }
}

この設計のメリットは大きいです。

テストでは

任意の状態を自由に作れる

からです。

状態ごとのUIテスト

例えば次のようなテストが書けます。

フォロー前

UserProfile(
state = UserProfileUiState(
name = "Taro",
isFollowing = false
),
onFollowClick = {}
)

期待UI

Follow

フォロー済み

UserProfile(
state = UserProfileUiState(
name = "Taro",
isFollowing = true
),
onFollowClick = {}
)

期待UI

Following

このように

UI状態ごとのテストが簡単に書ける

ようになります。

testTagを使う

実務では testTag を使うことも多いです。

Button(
modifier = Modifier.testTag("followButton"),
onClick = onFollowClick
)

テスト側

composeTestRule
.onNodeWithTag("followButton")
.performClick()

テキストに依存しないテストが書けます。

Composeテストを壊すアンチパターン

実務でよく見る失敗を紹介します。

アンチパターン① ViewModel取得 inside UI

viewModel()

UIテストが困難になります。

アンチパターン② collectAsState inside UI

viewModel.flow.collectAsState()

状態制御ができません。

アンチパターン③ MutableStateを渡す

MutableState<String>

UIが状態を書き換えてしまいます。

Composeテストが強い理由

実はComposeは

UIテストに非常に強い設計

になっています。

なぜなら、

UI = 関数

だからです。

つまり

入力 → UI

の関数テストになります。

従来のView UIとの違い

従来のAndroid UIでは、

Activity
Fragment
View
Adapter

などが絡みます。

UIテストを書くには

  • Activity起動
  • Fragment表示
  • View取得

などが必要でした。

しかしComposeでは

Composable単体

でテストできます。

これは非常に大きな違いです。

まとめ

ComposeでテストしやすいUIを書くための原則はシンプルです。

① UIは状態を受け取る

Composable(state)

② UIはイベントを通知する

onClick
onEvent

③ ViewModelをUIに持ち込まない

Screen → ViewModel
UI → 描画

この構造を守ると、

  • Previewしやすい
  • 再利用しやすい
  • テストしやすい

という Composeの理想設計 になります。

Composeは単なるUIツールではありません。

設計の質がそのままコード品質に直結するフレームワーク

です。

そして、

テストしやすいUIを書くことは
設計が正しいことの証明でもあります。

タイトルとURLをコピーしました