Anyone managed to get instrumented tests running w...
# multiplatform
r
Anyone managed to get instrumented tests running with KMP yet? We've got some bluetooth connectivity with an external device I'd like to test.
o
r
That's your project? Neat. Are there any examples on how to interact with UI nodes?
o
That's what I'm working on, yes. Though the examples above also cover the traditional JUnit4 style. UI = Compose? I have Compose examples and JUnit4 rules integration ready locally, and I'm in the process of consolidating them into a new release.
r
Yeah, mostly compose. We've got one or two views native, so being able to instrument everything else would be cool
I wonder if it's also possible to instrument OS dialogs (like photo picker etc.)
o
It's all pretty new in TestBalloon testballoon, so I can't tell about edge cases. Generally, anything that would traditionally work via a JUnit4 rule should also work with TestBalloon. In addition, you can always use traditional JUnit4 tests exclusively or side by side with TestBalloon, even in the same module. So you have options.
r
Because of the amount of external (hardware) interaction we have, we currently don't have any instrumented tests, so I don't mind experimenting with it. I probably won't be able to automate everything, but everything I don't have to test manually, I'm happy about 😄
o
In case you want to try with TestBalloon now, here's how you could do it even before the new release is out:
Copy code
package com.example

import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import de.infix.testBalloon.framework.TestDiscoverable
import de.infix.testBalloon.framework.TestSuite
import de.infix.testBalloon.framework.testSuite
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.test.runTest
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

val ComposeTestsWithTestBalloon by testSuite {
    @Composable
    fun ComposableUnderTest() {
        var text by remember { mutableStateOf("Idle") }
        Button(onClick = { text = "Success" }) {
            Text("Button")
        }
        Text(text)
    }

    testWithCompose("setup") {
        composeTestRule.setContent {
            ComposableUnderTest()
        }

        composeTestRule.onNodeWithText("Idle").assertExists()
    }

    testWithCompose("click") {
        composeTestRule.setContent {
            ComposableUnderTest()
        }

        composeTestRule.onNodeWithText("Button").performClick()
        composeTestRule.awaitIdle()
        composeTestRule.onNodeWithText("Success").assertExists()
    }
}

/**
 * Declares a [Test] with a [ComposeTestContext] as a child of this test suite.
 */
@TestDiscoverable
fun TestSuite.testWithCompose(
    name: String,
    composeTestRule: ComposeContentTestRule = createComposeRule(),
    action: suspend ComposeTestContext.() -> Unit
) = testWithJUnitRule(name, composeTestRule) {
    ComposeTestContext(composeTestRule).action()
}

class ComposeTestContext(val composeTestRule: ComposeContentTestRule)

/**
 * Declares a [Test] wrapped by a JUnit 4 [TestRule] as a child of this test suite.
 */
@TestDiscoverable
fun <Rule : TestRule> TestSuite.testWithJUnitRule(name: String, rule: Rule, action: suspend (Rule) -> Unit) =
    test(name) {
        val description = Description.createTestDescription(
            "${this@testWithJUnitRule.testElementPath}",
            name,
            "$testElementPath"
        )
        rule.apply(
            object : Statement() {
                override fun evaluate() {
                    runTest(coroutineContext.minusKey(CoroutineExceptionHandler.Key)) { action(rule) }
                }
            },
            description
        ).evaluate()
    }
Otherwise, you could always stick with JUnit4 and just use the build script from the above example to configure stuff. Of course, then you would miss out on testing 2025 style, like dynamically generated tests, hierarchy, coroutines out of the box, ...
In #C09FQGG85EC you can find a collection of links you might find useful. In particular, Ivan's article on Kotlin testing.
r
Oh neat, I'll take a look
👍 1