テストを同期する

Compose テストは、デフォルトで UI と同期されます。ComposeTestRule でアサーションまたはアクションを呼び出すと、テストは事前に同期され、UI ツリーがアイドル状態になるまで待機します。

通常は、何もする必要はありません。ただし、知っておくべきエッジケースがいくつかあります。

テストが同期されると、Compose アプリは仮想クロックを使用して時間を進めます。つまり、Compose テストはリアルタイムで実行されないため、可能な限り早く結果を出すことができます。

ただし、テストを同期するメソッドを使用しなかった場合は、再コンポジションが発生せず、UI が一時停止しているように見えます。

@Test
fun counterTest() {
    val myCounter = mutableStateOf(0) // State that can cause recompositions.
    var lastSeenValue = 0 // Used to track recompositions.
    composeTestRule.setContent {
        Text(myCounter.value.toString())
        lastSeenValue = myCounter.value
    }
    myCounter.value = 1 // The state changes, but there is no recomposition.

    // Fails because nothing triggered a recomposition.
    assertTrue(lastSeenValue == 1)

    // Passes because the assertion triggers recomposition.
    composeTestRule.onNodeWithText("1").assertExists()
}

この要件は Compose 階層にのみ適用され、アプリの他の部分には適用されません。

自動同期を無効にする

assertExists() などの ComposeTestRule を介してアサーションまたはアクションを呼び出すと、テストは Compose UI と同期されます。場合によっては、この同期を停止して、手動でクロックを制御できます。たとえば、UI がまだビジー状態である時点で、アニメーションの正確なスクリーンショットを撮る時間を制御できます。自動同期を無効にするには、mainClockautoAdvance プロパティを false に設定します。

composeTestRule.mainClock.autoAdvance = false

この場合、通常は手動で時間を進めます。advanceTimeByFrame() を使用してフレームを正確に 1 つだけ進めたり、advanceTimeBy() を使用して進める時間を指定したりできます。

composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.mainClock.advanceTimeBy(milliseconds)

アイドル状態のリソース

Compose は、テストと UI を同期することにより、すべてのアクションとアサーションがアイドル状態で実行され、必要に応じてクロックを待機させるか進めるようにすることができます。ただし、結果が UI 状態に影響する一部の非同期オペレーションは、テストによって認識されていないときにバックグラウンドで実行される可能性があります。

これらのアイドリング リソースをテストで作成して登録し、テスト対象のアプリがビジー状態かアイドル状態かを判断する際にそれらが考慮されるようにします。Espresso や Compose と同期されていないバックグラウンド ジョブを実行する場合など、追加のアイドリング リソースを登録する必要がない限り、特に対応は必要ありません。

この API は Espresso のアイドリング リソースによく似ており、テスト対象がアイドル状態かビジー状態かを示します。Compose テストルールを使用して、IdlingResource の実装を登録します。

composeTestRule.registerIdlingResource(idlingResource)
composeTestRule.unregisterIdlingResource(idlingResource)

手動同期

特定のケースでは、Compose UI をテストの他の部分またはテスト対象のアプリと同期する必要があります。

waitForIdle() 関数は Compose がアイドル状態になるまで待機しますが、この関数は autoAdvance プロパティに依存します。

composeTestRule.mainClock.autoAdvance = true // Default
composeTestRule.waitForIdle() // Advances the clock until Compose is idle.

composeTestRule.mainClock.autoAdvance = false
composeTestRule.waitForIdle() // Only waits for idling resources to become idle.

どちらの場合も、waitForIdle() は保留中の描画パスとレイアウトパスを待機します。

また、advanceTimeUntil() を使用して、特定の条件が満たされるまでクロックを進めることもできます。

composeTestRule.mainClock.advanceTimeUntil(timeoutMs) { condition }

なお、指定された条件は、このクロックの影響を受ける可能性のある状態をチェックする必要があります(Compose の状態でのみ機能します)。

条件を待つ

データの読み込みや Android の測定や描画(つまり、Compose の外部の測定や描画)などの外部処理に依存する条件では、waitUntil() のようなより一般的なコンセプトを使用する必要があります。

composeTestRule.waitUntil(timeoutMs) { condition }

また、任意の waitUntil ヘルパーを使用することもできます。

composeTestRule.waitUntilAtLeastOneExists(matcher, timeoutMs)

composeTestRule.waitUntilDoesNotExist(matcher, timeoutMs)

composeTestRule.waitUntilExactlyOneExists(matcher, timeoutMs)

composeTestRule.waitUntilNodeCount(matcher, count, timeoutMs)

参考情報

  • Android でアプリをテストする: Android のテストのメインランディング ページでは、テストの基礎と手法について幅広い情報を確認できます。
  • テストの基礎: Android アプリのテストの基本コンセプトについて学びます。
  • ローカルテスト: 一部のテストは、自分のワークステーションのローカルに実行できます。
  • インストルメンテーション テスト: インストルメンテーション テストも実行することをおすすめします。つまり、デバイス上で直接実行されるテストです。
  • 継続的インテグレーション: 継続的インテグレーションにより、テストをデプロイ パイプラインに統合できます。
  • さまざまな画面サイズをテストする: ユーザーが利用できるデバイスが多いため、さまざまな画面サイズでテストする必要があります。
  • Espresso: Espresso の知識はビューベースの UI を対象としていますが、Compose のテストのいくつかの側面においても役立ちます。