Testing Compose Screens with robot testing pattern
I’ve recently came across the following article https://tomasrepcik.dev/blog/2024/2024-02-06-robot-pattern-compose-testing/ which is a good article about using robot pattern for e2e (end to end) testing in compose.
Few days before that I was rewatching the great Testing robots presentation by Jake Wharton (https://jakewharton.com/testing-robots/) and thinking how could I apply that not only to e2e tests but also to compose tests about specific composables, like a screen content.
Why
Using robots provides some architecture for the tests as we do with the rest of our code. Like what we do when we use presentation patterns to separate the what (model) from the how (presenters) in robot pattern the test should describe the WHAT and the robot the HOW.
I highly recommend watching Jake Wharton’s presentation, where he explains a concept in a manner that I find difficult to articulate myself. The presentation includes code examples demonstrating the usage of test robots written in Kotlin.
The way I see it: Imagine you’ve got this awesome kitchen cooking machine that effortlessly whips up gourmet meals with just a few button presses, you only need to know which buttons to press and that buttons would be the what and not the how.
Talk is cheap. Show me the code. — Linus Torvalds
I decided to try to apply that in a side project which is just a sample app in compose using the marvel api at https://github.com/jeprubio/marvel-compose .
Let’s create our first test, HeroListScreenTest, which is a screen with a list of marvel characters. As we are testing composables we can use the libs from the official compose testing documentation and create our composeTestRule in each testing file:
class HeroListScreenTest {
@get:Rule
val composeTestRule = createComposeRule()and then pass that compose rule to the robots we use. One test could be:
heroListRobot(composeTestRule) {
} verify {
characterIsDisplayed("character 1")
}this way the factory that creates the robot receives the composeTestRule:
fun heroListRobot(composeTestRule: ComposeContentTestRule, func: HeroListRobot.() -> Unit) =
HeroListRobot(composeTestRule).apply { func() }and passes it to the robot that can use it to set the layout and perform all the instructions:
class HeroListRobot(private val composeTestRule: ComposeContentTestRule) {
init {
composeTestRule.setContent {
MarvelComposeTheme {
HeroListScreenContent(...)
}
}
}
fun screenAction() {
composeTestRule...
}
fun anotherScreenAction() {
composeTestRule...
}
infix fun verify(func: HeroListResultRobot.() -> Unit) =
HeroListResultRobot(composeTestRule).apply { func() }
}
class HeroListResultRobot(private val composeTestRule: ComposeContentTestRule) {
fun screenAssertion(name: String) {
composeTestRule....
}
fun anotherScreenAssertion() {
composeTestRule...
}
}so that we can create our tests using that robots, like the following one:
@Test
fun whenMagnifierTappedItShowsSearchTextInput() {
heroListRobot(composeTestRule) {
onMagnifierTapped()
} verify {
searchIsDisplayed()
}
}In that test we can know the what the test does. It’s using the HeroListRobot (so it’s loading the HeroListScreenContent), it taps on the magnifier and verifies that a search is displayed. How is that done? It doesn’t matter, the robot will know that.
Get Jeprubio’s stories in your inbox
Join Medium for free to get updates from this writer.
If we then check the robot code:
fun onMagnifierTapped() {
composeTestRule.onNodeWithContentDescription(searchContentDescription).performClick()
}we can see that the robot is invoking performClick on that element
fun searchIsDisplayed() {
composeTestRule.onNodeWithContentDescription(getString(R.string.search_text)).assertIsDisplayed()
}and then verifying the search_text element is displayed.
Possible improvement
If we don’t want to pass the composeTestRule explicitly to the factory method we can make it an extension function of the test where we want to use it:
fun HeroListScreenTest.heroListRobot(func: HeroListRobot.() -> Unit) =
HeroListRobot(composeTestRule).apply { func() }so that then the test reads:
@Test
fun whenMagnifierTappedItShowsSearchTextInput() {
heroListRobot {
onMagnifierTapped()
} verify {
searchIsDisplayed()
}
}However, as robots can be used in more than one test that means they could have multiple factories if we want to do that and use them from multiple tests.
So I would keep this in mind knowing the pros and cons to decide whether to use this latest approach or not.
The robot testing pattern facilitates a clearer separation of concerns in testing code. By abstracting implementation details, the tests can concentrate on defining the behaviour of UI components rather than dwelling on the specifics of how actions are executed and assertions are verified. This abstraction enhances test maintainability and readability, contributing to more efficient testing practices for UI tests.
You can check the full screen test used in the sample and the robot that screen uses. And the full side project repository.





