Compose テスト API(createComposeRule、createAndroidComposeRule、runComposeUiTest、runAndroidComposeUiTest など)の v2 バージョンが利用可能になり、コルーチンの実行をより細かく制御できるようになりました。このアップデートでは、API サーフェス全体が複製されるわけではなく、テスト環境を確立する API のみが更新されます。
v1 API は非推奨であり、新しい API への移行を強くおすすめします。移行すると、テストが標準のコルーチン動作に沿っていることが検証され、将来の互換性の問題が回避されます。非推奨の v1 API のリストについては、API マッピングをご覧ください。
これらの変更は androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ と androidx.compose.ui:ui-test:1.11.0-alpha03+ に含まれています。
v1 API は UnconfinedTestDispatcher に依存していましたが、v2 API は実行中の構成にデフォルトで StandardTestDispatcher を使用します。この変更により、Compose テストの動作が標準の runTest API と一致し、コルーチンの実行順序を明示的に制御できるようになります。
API のマッピング
v2 API にアップグレードする際は、通常、検索と置換を使用してパッケージのインポートを更新し、新しいディスパッチャーの変更を採用できます。
または、次のプロンプトを使用して、Compose テスト API の v2 への移行を Gemini に依頼します。
AI プロンプト
v1 テスト API から v2 テスト API に移行する
このプロンプトは、このガイドを使用して v2 テスト API に移行します。
Migrate to Compose testing v2 APIs using the official
migration guide.次の表を使用して、非推奨となった v1 API を v2 の代替 API にマッピングします。
非推奨(v1) |
Replacement(v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下位互換性と例外
既存の v1 API は非推奨になりましたが、既存の動作を維持し、互換性を破る変更を防ぐために、引き続き UnconfinedTestDispatcher を使用してください。
デフォルトの動作が変更された唯一の例外は次のとおりです。
AndroidComposeUiTestEnvironment クラスでコンポジションを実行するために使用されるデフォルトのテスト ディスパッチャが、UnconfinedTestDispatcher から StandardTestDispatcher に切り替わりました。これは、コンストラクタを使用してインスタンスを作成する場合、または AndroidComposeUiTestEnvironment をサブクラス化してそのコンストラクタを呼び出す場合に影響します。
主な変更点: コルーチンの実行への影響
API の v1 と v2 の主な違いは、コルーチンのディスパッチ方法です。
- v1 API(
UnconfinedTestDispatcher): コルーチンが起動されると、現在のスレッドで直ちに実行され、多くの場合、テストコードの次の行が実行される前に終了していました。本番環境の動作とは異なり、この即時実行では、ライブ アプリケーションで発生する実際のタイミングの問題や競合状態が誤ってマスクされる可能性があります。 - v2 API(
StandardTestDispatcher): コルーチンが起動されると、キューに入れられ、テストで仮想クロックが明示的に進められるまで実行されません。標準の Compose テスト API(waitForIdle()など)はすでにこの同期を処理しているため、これらの標準 API に依存するほとんどのテストは、変更なしで引き続き動作します。
一般的なエラーとその修正方法
v2 にアップグレードした後にテストが失敗する場合は、次のパターンに該当する可能性があります。
- 失敗: タスク(ViewModel がデータを読み込むなど)を起動しますが、データがまだ「読み込み中」の状態であるため、アサーションがすぐに失敗します。
- 原因: v2 API では、コルーチンはすぐに実行されず、キューに登録されます。タスクはキューに登録されましたが、結果がチェックされる前に実行されませんでした。
- 修正: 時間を明示的に進めます。v2 ディスパッチャーに、作業を実行するタイミングを明示的に伝える必要があります。
以前のアプローチ
v1 では、タスクはすぐに起動して終了しました。v2 では、loadData() がまだ実行されていないため、次のコードは失敗します。
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
おすすめの方法
waitForIdle または runOnIdle を使用して、アサーションの前にキューに登録されたタスクを実行します。
オプション 1: waitForIdle を使用して、UI がアイドル状態になるまでクロックを進め、コルーチンが実行されたことを確認します。
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
オプション 2: runOnIdle を使用すると、UI がアイドル状態になった後で、UI スレッドでコードブロックが実行されます。
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
手動同期
自動進行が無効になっている場合など、手動同期を伴うシナリオでは、テストクロックが一時停止しているため、コルーチンを起動してもすぐに実行されません。仮想クロックを進めずにキュー内のコルーチンを実行するには、runCurrent() API を使用します。これは、現在の仮想時間にスケジュールされたタスクを実行します。
composeTestRule.mainClock.scheduler.runCurrent()
UI が安定するまでテストクロックを進める waitForIdle() とは対照的に、runCurrent() は現在の仮想時間を維持しながら保留中のタスクを実行します。この動作により、クロックがアイドル状態に進んだ場合にスキップされる中間状態の検証が可能になります。
テスト環境で使用される基盤となるテスト スケジューラが公開されます。このスケジューラは、Kotlin runTest API と組み合わせて使用して、テストクロックを同期できます。
runComposeUiTest に移行する
Kotlin runTest API とともに Compose テスト API を使用している場合は、runComposeUiTest に切り替えることを強くおすすめします。
以前のアプローチ
createComposeRule を runTest と組み合わせて使用すると、Compose 用とテスト コルーチン スコープ用の 2 つの別々のクロックが作成されます。この構成では、テスト スケジューラを手動で同期する必要があります。
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
おすすめの方法
runComposeUiTest API は、独自の runTest スコープ内でテストブロックを自動的に実行します。テストクロックは Compose 環境と同期されるため、スケジューラを手動で管理する必要がなくなりました。
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }