Compose 測試 API 的 v2 版本 (createComposeRule、createAndroidComposeRule、runComposeUiTest、runAndroidComposeUiTest 等) 現已推出,可提升對協同程式執行的控制能力。這項更新不會複製整個 API 介面,只會更新用於建立測試環境的 API。
v1 API 已淘汰,強烈建議您遷移至新版 API。遷移作業會驗證測試是否符合標準協同程式行為,並避免日後發生相容性問題。如需已淘汰的 v1 API 清單,請參閱「API 對應」。
v1 API 依賴 UnconfinedTestDispatcher,而 v2 API 預設會使用 StandardTestDispatcher 執行組合。這項變更可讓 Compose 測試行為與標準 runTest API 保持一致,並明確控管協同程式的執行順序。
設定測試環境
Compose 測試 v2 API 會使用 ComposeUiTestConfig 自訂測試環境。用於建立測試設定函式的 API (例如 createComposeRule、runComposeUiTest 和其他相關 API) 會接受 ComposeUiTestConfig。這個設定物件會將環境相關的 API (例如 effectContext、runTestContext 和 testTimeout) 合併為單一物件。
設定模型也會管理 inputMode。Compose 測試 V2 API 會在每個測試開始時,預設強制執行 InputMode.Touch,確保確定性並防止輸入模式狀態在測試之間洩漏。
ComposeUiTestConfig 是 Compose 測試 v2 API 的一部分,預設使用 StandardTestDispatcher。如果測試使用 v1 API,請先參閱「遷移至 v2 測試 API」,再採用 ComposeUiTestConfig。
遷移至 ComposeUiTestConfig
在測試中建立設定函式的超載中,接受個別設定參數 (例如 effectContext、runTestContext 或 testTimeout) 的多個超載已遭淘汰。請更新測試,改用 ComposeUiTestConfig,如下例所示:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
預設輸入模式
如果測試依賴透過檢測設備 API 設定的非觸控輸入模式,則在測試開始前,測試可能會在遷移期間失敗。在測試的設定函式中,系統預設會在每個測試開始時強制執行 InputMode.Touch,以提高確定性並防止狀態洩漏,同時覆寫周遭裝置狀態和測試前設定。
如要解決這個問題,請在 ComposeUiTestConfig 中指定必要的輸入模式:
class FocusTest { @get:Rule val rule = createComposeRule( config = ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) @Test fun testFocus() {} }
如要為個別測試案例 (而非整個測試類別) 設定輸入模式,請將 ComposeUiTestConfig 傳遞至 runComposeUiTest:
class FocusTest { @Test fun testTouchMode() = runComposeUiTest { // Runs with the default InputMode.Touch } @Test fun testKeyboardMode() = runComposeUiTest( ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) { // Runs with InputMode.Keyboard } }
如要瞭解其他遷移問題和解決方法,請參閱「未通過審查的常見原因和解決方法」。
遷移至 v2 測試 API
升級至 v2 API 時,您通常可以使用「尋找 + 取代」更新套件匯入項目,並採用新的調度器變更。
或者,您也可以使用下列提示,要求 Gemini 將 Compose 測試 API 遷移至第 2 版:
AI 提示詞
從 v1 測試 API 遷移至 v2 測試 API
這個提示會使用本指南,將您遷移至 v2 測試 API。
Migrate to Compose testing v2 APIs using the official
migration guide.請使用下表,將已淘汰的 v1 API 對應至 v2 替代 API:
已淘汰 (v1) |
取代 (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
回溯相容性和例外狀況
現有的第 1 版 API 已淘汰,但請繼續使用 UnconfinedTestDispatcher 維持現有行為,並避免重大變更。
預設行為變更的唯一例外狀況如下:
用於在 AndroidComposeUiTestEnvironment 類別中執行組合的預設測試調度器,已從 UnconfinedTestDispatcher 變更為 StandardTestDispatcher。如果您使用建構函式建立例項,或是將 AndroidComposeUiTestEnvironment 子類別化並呼叫該建構函式,就會受到影響。
主要異動:對協同程式執行的影響
API 第 1 版和第 2 版的主要差異在於協同程式的調度方式:
- v1 API (
UnconfinedTestDispatcher):啟動協同程式後,系統會立即在目前執行緒上執行,通常會在下一行測試程式碼執行前完成。與實際工作環境中的行為不同,這種立即執行可能會無意間遮蓋實際時間問題或競爭條件,而這些問題或條件會在實際應用程式中發生。 - v2 API (
StandardTestDispatcher):啟動協同程式時,系統會將其排入佇列,並在測試明確推進虛擬時鐘前不會執行。標準 Compose 測試 API (例如waitForIdle()) 已處理這項同步作業,因此大部分依賴這些標準 API 的測試應可繼續運作,不必進行任何變更。
未通過審查的常見原因和解決方法
升級至 v2 後,如果測試失敗,可能會有下列模式:
- 失敗:您啟動工作 (例如 ViewModel 載入資料),但由於資料仍處於「載入中」狀態,因此斷言會立即失敗。
- 原因:使用第 2 版 API 時,系統會將協同程式加入佇列,而不是立即執行。工作已加入佇列,但檢查結果前從未實際執行。
- 修正:明確推進時間。您必須明確告知 v2 分派器何時執行工作。
先前的做法
在第 1 版中,工作會立即啟動並完成。在第 2 版中,下列程式碼會失敗,因為 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()
與 waitForIdle() 不同,runCurrent() 會執行待處理的工作,同時維持目前的虛擬時間,而則會推進測試時鐘,直到 UI 穩定為止。如果時鐘前進至閒置狀態,系統會略過中間狀態,但這項行為可讓您驗證這些狀態。
公開測試環境中使用的基礎測試排程器。這個排程器可與 Kotlin runTest API 搭配使用,同步測試時鐘。
遷移至 runComposeUiTest
如果您同時使用 Compose 測試 API 和 Kotlin runTest API,強烈建議改用 runComposeUiTest。
先前的做法
搭配使用 createComposeRule 和 runTest 會建立兩個不同的時鐘:一個用於 Compose,另一個用於測試協同程式範圍。這項設定會強制您手動同步測試排程器。
@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() } }