如何测试包含rememberLauncherForActivityResult的Jetpack Compose组件?
测试调用rememberLauncherForActivityResult的Compose可组合项解决方案
核心思路
无需依赖真实外部Activity启动,而是模拟Activity Result的回调结果,验证可组合项在收到回调后的行为是否符合预期——因为Compose测试规则无法跨Activity交互,直接测试外部Activity不在其能力范围内。
步骤1:重构可组合项,提升可测试性
先抽离回调逻辑,让可组合项的行为更易被测试:
import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.compose.runtime.Composable import androidx.compose.material3.Button import androidx.compose.material3.Text @Composable fun PickFileButton( onFileSelected: (Uri?) -> Unit = {} // 抽离回调,方便测试时注入验证逻辑 ) { val launcher = rememberLauncherForActivityResult(CreateDocument("text/plain")) { uri -> onFileSelected(uri) } Button(onClick = { launcher.launch("default_file_name.txt") }) { Text("Pick File") } }
步骤2:用ActivityResultRegistry模拟回调
创建测试用的ActivityResultRegistry,手动触发回调,跳过真实的CreateDocument Activity启动流程:
import android.net.Uri import androidx.activity.result.ActivityResultRegistry import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.core.app.ApplicationProvider import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify class TestPickFileButton { @get:Rule val composeTestRule = createComposeRule() @Test fun testPickFileButton_triggersCallbackOnResult() { // 1. 创建模拟回调,用于验证是否被触发 val mockOnFileSelected: (Uri?) -> Unit = mock() // 2. 自定义测试用Registry,拦截launch调用并直接触发回调 val testRegistry = object : ActivityResultRegistry() { override fun <I, O> register( key: String, lifecycleOwner: androidx.lifecycle.LifecycleOwner, contract: androidx.activity.result.contract.ActivityResultContract<I, O>, callback: androidx.activity.result.ActivityResultCallback<O> ): androidx.activity.result.ActivityResultLauncher<I> { return object : androidx.activity.result.ActivityResultLauncher<I>() { override fun launch(input: I, options: androidx.activity.result.ActivityResultLauncher.IgnoreOptions?) { // 模拟返回测试用Uri val testUri = Uri.parse("content://test/default_file_name.txt") callback.onActivityResult(testUri as O) } override fun unregister() {} override fun getContract(): androidx.activity.result.contract.ActivityResultContract<I, O> = contract } } } // 3. 设置Compose内容,注入测试用Registry composeTestRule.setContent { androidx.activity.compose.LocalActivityResultRegistryProvider(testRegistry) { PickFileButton(onFileSelected = mockOnFileSelected) } } // 4. 执行点击操作 composeTestRule.onNodeWithText("Pick File").performClick() // 5. 验证回调是否被正确触发 val expectedUri = Uri.parse("content://test/default_file_name.txt") verify(mockOnFileSelected).invoke(expectedUri) } }
步骤3:验证文件创建逻辑(若有)
如果回调中包含文件写入等业务逻辑,将这部分逻辑抽离到独立类,通过Mock验证方法调用:
// 抽离的文件处理类 import android.content.Context import android.net.Uri class FileManager(private val context: Context) { fun writeFile(uri: Uri, content: String) { context.contentResolver.openOutputStream(uri)?.use { it.write(content.toByteArray()) } } } // 修改可组合项,注入FileManager import androidx.compose.ui.platform.LocalContext @Composable fun PickFileButton( fileManager: FileManager = FileManager(LocalContext.current), defaultContent: String = "test content" ) { val launcher = rememberLauncherForActivityResult(CreateDocument("text/plain")) { uri -> uri?.let { fileManager.writeFile(it, defaultContent) } } Button(onClick = { launcher.launch("default_file_name.txt") }) { Text("Pick File") } } // 测试文件创建逻辑 @Test fun testPickFileButton_writesFileOnSelection() { val mockFileManager: FileManager = mock() val testUri = Uri.parse("content://test/default_file_name.txt") val testRegistry = object : ActivityResultRegistry() { override fun <I, O> register(...) = object : androidx.activity.result.ActivityResultLauncher<I>() { override fun launch(input: I, options: androidx.activity.result.ActivityResultLauncher.IgnoreOptions?) { callback.onActivityResult(testUri as O) } override fun unregister() {} override fun getContract(): androidx.activity.result.contract.ActivityResultContract<I, O> = contract } } composeTestRule.setContent { androidx.activity.compose.LocalActivityResultRegistryProvider(testRegistry) { PickFileButton(fileManager = mockFileManager) } } composeTestRule.onNodeWithText("Pick File").performClick() // 验证文件写入方法是否被正确调用 verify(mockFileManager).writeFile(testUri, "test content") }
关键注意点
- 不要尝试用Compose测试规则控制外部Activity,这不属于它的测试范畴
- 聚焦验证可组合项自身的逻辑:点击是否触发launch、回调是否被正确处理
- 抽离业务逻辑到独立类,既降低耦合,也让测试更简单
内容的提问来源于stack exchange,提问作者David Hadley
相关产品推荐
相关产品推荐

