You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 10:12:08