如何测试Swing与Compose Desktop混合开发的应用程序
测试嵌入Swing的Compose Desktop组件方案
针对你在迁移Swing到Compose Desktop过程中,需要测试混合Swing与嵌入式Compose组件的场景,以下是两种可行方案,均能复用ComposeTestRule的匹配器与断言能力:
方案一:直接在测试中整合Swing初始化与Compose测试API
无需额外自定义规则,直接在测试方法内完成Swing UI的初始化,结合Compose测试API执行交互与断言:
import androidx.compose.ui.test.junit4.createComposeRule import org.junit.Rule import org.junit.Test import javax.swing.SwingUtilities import javax.swing.JFrame import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.assertExists import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.material.MaterialTheme import androidx.compose.material.TextField import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.test.testTag class EmbeddedComposeTest { @get:Rule val composeTestRule = createComposeRule() @Test fun `text field should be initialised to bar and editable in Swing container`() { // 在Swing事件调度线程初始化包含ComposePanel的JFrame SwingUtilities.invokeLater { val composePanel = ComposePanel().apply { setContent { MyTextField() } } JFrame("Test Frame").apply { add(composePanel) setSize(400, 200) isVisible = true } } // 等待Compose重组与Swing UI渲染完成 composeTestRule.waitForIdle() // 复用Compose测试API执行交互与断言 composeTestRule.apply { onNodeWithText("bar").assertExists() onNodeWithTag("myTextField").performTextInput("foo ") onNodeWithText("foo bar").assertExists() } // 测试结束清理Swing窗口,避免资源泄漏 SwingUtilities.invokeLater { JFrame.getFrames().forEach { it.dispose() } } } } @Composable @Preview fun MyTextField() { var text by remember { mutableStateOf("bar") } MaterialTheme { TextField( value = text, onValueChange = { text = it }, modifier = Modifier.testTag("myTextField") ) } }
方案二:自定义测试规则复用Swing初始化逻辑
如果需要在多个测试中复用Swing UI的初始化逻辑,可以自定义整合Swing与Compose的测试规则:
自定义测试规则
import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement import javax.swing.SwingUtilities import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.createComposeRule class SwingComposeTestRule( private val composeRule: ComposeTestRule = createComposeRule() ) : TestRule { override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { // 确保测试逻辑在Swing事件调度线程执行 SwingUtilities.invokeAndWait { composeRule.apply { base.evaluate() } } } } } // 暴露Compose测试规则供测试方法使用 fun compose() = composeRule }
使用自定义规则的测试示例
class EmbeddedComposeWithCustomRuleTest { @get:Rule val testRule = SwingComposeTestRule() @Test fun `test embedded text field interactions`() { // 初始化Swing UI SwingUtilities.invokeLater { val composePanel = ComposePanel().apply { setContent { MyTextField() } } JFrame("Test Frame").apply { add(composePanel) setSize(400, 200) isVisible = true } } testRule.compose().waitForIdle() // 执行Compose测试断言与交互 testRule.compose().run { onNodeWithText("bar").assertExists() onNodeWithTag("myTextField").performTextInput("foo ") onNodeWithText("foo bar").assertExists() } // 清理资源 SwingUtilities.invokeLater { JFrame.getFrames().forEach { it.dispose() } } } }
核心注意事项
- 必须在Swing事件调度线程(EDT)操作UI:所有Swing组件的创建、修改都要通过
SwingUtilities.invokeLater或invokeAndWait执行,避免线程安全问题 - 等待UI空闲:调用
composeTestRule.waitForIdle()确保Compose重组完成、Swing UI渲染完毕后再执行断言,避免测试不稳定 - 资源清理:测试结束后关闭所有Swing窗口,防止内存泄漏
内容的提问来源于stack exchange,提问作者Lindsay Peters
相关产品推荐
相关产品推荐

