:testballoon: :mega: Call for ideas! As you may k...
# testballoon
o
testballoon 📣 Call for ideas! As you may know, along with @Bernd Prünster I'll be giving a talk "TestBalloon: Kotlin testing is easier (and more fun) than you think" at KotlinConf '26 in May. The auditorium has a capacity of 1430 seats, so we'd love to show the most relevant aspects possible in 45 minutes. Of course, there are more ideas than could fit into a session slot, but still, yours may be even better. This is where your experiences come in, so let's all grab the opportunity to shape this talk. Questions to get started: • What concise examples would you like folks unaware of TestBalloon to see? • What's the most important value TestBalloon offers when compared to another (specific) test framework? • What would be the reasons not to use TestBalloon (yet)? Beyond these questions, feel free to address anything that should be included for an audience across platforms (latest stats: 40% server, 37% mobile, 23% web, 10% desktop). Looking forward to your feedback (🧵)! kodee loving
🙌 1
K 2
a
What brought me to TestBaloon was the ability to be able to write one test and run it for mobile and desktop form factors. We have a compose multiplatform setup and at least 60% of our forms have similar look and behaviour for mobile and desktop form factors. We dreaded writing tests for mobile then for desktop that have an exact set of instructions and using testballoon made it easy to write these tests once and see them run in both of these form factors. I think that's one of the usecase you could show at KotlinConf
kodee loving 2
o
Thanks! Do you have a short code example you could share (doesn't need to include all the details)? What did you try/use before TestBalloon was a thing?
a
We were using the built in kotlin test framework before
Copy code
val EmailSignInTestSuite by testSuite(displayName = "When a user opens the sign-in screen") {
    academiaComposeTest("Should be able to sign in") {
        goto { authentication.signIn() }

        awaitUntil { element("Login").exists() }

        type("<mailto:demo@academia.co.tz|demo@academia.co.tz>", into = "account")
        type("<mailto:demo@academia.co.tz|demo@academia.co.tz>", into = "password")

        click("Login")

        awaitUntil {
            engine.navigator.current().toString().endsWith("/dashboard")
        }
    }
}
E.g. This code snippet helps us tests • Desktop (Minimized to a mobile screen size experience) • Desktop (Maximized to a richer desktop experience) • Android Mobile • Android Tablet • Web (Minimised to a mobile screen size) • Web (Maximized to a richer desktop experience) • IOS Mobile • IOS Tablet
o
So am I correct to assume that prior to TestBalloon, assuming
academiaComposeTest
calls
runComposeUiTest
, things looked like this?
Copy code
class EmailSignInTestSuite {
    @Test
    fun When_a_user_opens_the_sign_in_screen() {
        academiaComposeTest("Should be able to sign in") {
            // ...
        }
    }
}
a
Prior to that we had something like that, yes. But we had no way to run the same test for different form factors. We kinda ignored testing the desktop form factors
o
That's actually something I'm hearing frequently: If tests are too cumbersome to write, they won't get written. In your case: What was blocking a single test function for all form factors before?
a
We were interested more in failure cases (as every developer should). But it was tricky to determine which form factor was failing with the kotlin test framework. We needed to be able to tell that, this test is passing on one form factor but failing on another. We tried difference scenarios like • Write one normal function and call it on two seperate test functions (functions annotated with @Test). • Copy and paste the same test for different form factors But non of that seemed scalable and maintainable. And that is how we landed into testballoon
Copy code
private enum class ViewPort(val width: Int, val height: Int) : WindowInfo {
    Portrait(800, 1200),
    Landscape(1200, 800);

    override val isWindowFocused: Boolean = true
    override val containerSize: IntSize = IntSize(width, height)
}

@TestRegistering
fun TestFixture.Scope<suspend ExecutionScope.(AcademiaEngine) -> Unit>.academiaComposeTest(
    name: String,
    start: AcademiaDestinations.() -> String = { authentication.signIn() },
    action: suspend AcademiaComposeUiTest.() -> Unit
) {
    val files = VirtualFileManager()
    val destination = AcademiaDestinations("")

    for (vp in ViewPort.entries) test("$name [${vp.name}]") { engine ->
        val config = ClientConfig(
            api = "",
            start = destination.start(),
            window = StartWindow.Desktop1024x768
        )
        runComposeUiTest {
            setContent {
                CompositionLocalProvider(LocalWindowInfo provides vp) {
                    val panel = rememberPanelController(engine, config, files)
                    MaterialTheme(typography = MarkProTypography()) {
                        Academia(panel, null)
                    }
                }
            }
            action(AcademiaComposeUiTest(this, engine))
        }
    }
}
Our configuration now looks like this. Simple but very powerful
o
Ah, I see. Now you have a parameterized test series and this is what
kotlin.test
cannot do. Thanks a lot! That's a good example!