0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Robolectric + Compose UI Testで Compose did not get idle にハマった話

0
Last updated at Posted at 2026-06-17

Jetpack Compose + Robolectric でテストを書いていたところ、ある日突然 AppNotIdleException が発生するようになりました。

最初は Compose のレイアウト構成が原因だと思っていたのですが、検証を進めるうちに原因がまったく別のところにあることが分かってきました。

この記事では、原因を切り分けていった過程と、現時点で分かっていることをまとめます。

発生した例外

テストは単体では成功するものの、他のテストと一緒に実行すると次の例外が発生しました。

androidx.test.espresso.AppNotIdleException:
Compose did not get idle after 60 SECONDS.

例えば、

./gradlew testDebugUnitTest --tests "*.PlainComposeTest"

では成功しますが、

./gradlew testDebugUnitTest

では失敗します。

つまり、

  • 単体実行 → PASS
  • 全体実行 → FAIL

という状況でした。


最初に疑ったこと

最初は Compose のレイアウト構造が原因だと思いました。

例えば次のようなコードです。

@Composable
fun Screen(visible: Boolean) {
    if (!visible) {
        return
    }

    Text("Hello")
}

また、

@Composable
fun Screen() {
    Text("Hello")
}

では失敗するのに、

@Composable
fun Screen() {
    Scaffold { padding ->
        Box(
            modifier = Modifier.padding(padding),
        ) {
            Text("Hello")
        }
    }
}

では成功するケースもありました。

このため、

  • Scaffold
  • if による早期 return

あたりを疑っていました。


最小再現プロジェクトを作成

原因を切り分けるため、検証専用のリポジトリを作成しました。

目的は

  • 余計なライブラリを排除する
  • 一つずつ条件を追加する
  • 再現条件を最小化する

ことです。


切り分け

Room の書き込み

Room の INSERT だけを実行するテスト。

結果

✅ 再現しない


Room の読み込み

Room の SELECT のみ実行。

結果

✅ 再現しない


ViewModel

ViewModel を生成するだけ。

結果

✅ 再現しない


ViewModel + Room

Room のデータを ViewModel から取得。

結果

✅ 再現しない


mutableStateOf

ViewModel 内で

var uiState by mutableStateOf(...)

を更新する。

結果

AppNotIdleException


StateFlow

同じコードを

private val _uiState = MutableStateFlow(...)
val uiState = _uiState.asStateFlow()

へ変更。

結果

✅ 再現しない


ViewModel を使わない場合

さらに ViewModel すら使わず、

class MutableStateHolder {
    var state by mutableStateOf(0)

    fun update() {
        state = 1
    }
}

だけを実行。

その後 Compose UI Test を実行すると、

AppNotIdleException

一方、

class StateFlowHolder {
    private val _state = MutableStateFlow(0)
    val state = _state.asStateFlow()

    fun update() {
        _state.value = 1
    }
}

では

✅ PASS

となりました。


現時点で分かっていること

再現するケース

ケース 結果
ViewModel + mutableStateOf
mutableStateOf を持つ通常クラス

再現しないケース

ケース 結果
Room の読み書き
ViewModel のみ
StateFlow を使用

Scaffold について

後続の Compose UI Test を

Scaffold {
    ...
}

でラップすると失敗を回避できるケースがありました。

ただし、

  • Scaffold 単体では再現しない
  • Scaffold の有無だけでは説明できない

ため、

Scaffold は根本原因ではなく、Compose UI Test の Idling 判定に影響しているだけではないかと考えています。


現時点の仮説

今回の調査結果では、

Robolectric Test
        ↓
mutableStateOf を更新
        ↓
後続の Compose UI Test
        ↓
AppNotIdleException

という条件で再現しています。

一方、

StateFlow を更新
        ↓
後続の Compose UI Test

では再現していません。

そのため、現時点では Compose Runtime の Snapshot State (mutableStateOf) と Robolectric / Compose UI Test の Idling 判定の組み合わせに何らかの問題があるのではないかと考えています。


今後

まだ根本原因までは特定できていません。

今後はさらに検証を進め、

  • Compose Runtime
  • Compose UI Test
  • Robolectric
  • Espresso Idling Resource

のどこに起因するのかを調査していく予定です。

もし同じ現象に遭遇した方がいれば、ぜひ情報をいただけると嬉しいです。

また、検証用リポジトリも公開しているので、再現確認などに利用していただければと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?