Testing Jetpack Compose

Tony Owen
2 min readJun 28, 2022

--

So far in the declarative UI world, I’ve had one problem… testing. I don’t want to have to run UI tests (on device / emulator) for everything.

It’s now far too easy to do something like this:

@Composable
fun MyComponent(viewModel: MyViewModel) {
Text(if(viewModel.someBool) "Hello" else "World"
}

So we can end up with untested logic inside our views.

I’ve seen this problem also in SwiftUI; But not my darling Flutter because Widget Testing ❤️ … well it turns out there is a way for Compose using Robolectric…

Lets start with dependencies (version at the time of writing are the latest)

testImplementation 'org.robolectric:robolectric:4.8.1'
testImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
debugImplementation 'androidx.compose.ui:ui-test-manifest:1.1.1'

Then we can move onto test setup, as this requires a bit of boiler plate I decided to write a base class …. I know, I know… Composition over inheritance 😱 ... I don’t care. So here’s the base compose test class

@RunWith(RobolectricTestRunner::class)
@Config(application = MyTestApplication::class,
sdk = [28],
instrumentedPackages = [
"androidx.loader.content"
])
abstract class MyComposeTest {
@get:Rule
val composeTestRule: createComposeTestRule()
@Before
open fun setup() {
MockKAnnotations.init(this)
}
}

Above we’re telling the test to run with Robolectric, giving a test application to avoid using the real application. When I tried this I got errors relating to “androidx.loader.content” so someone on StackOverflow (of course) had this snippet available.

Then we create the compose test rule to use inside our tests.

Now on to a real test:

class MyTest: MyComposeTest() {
@RelaxedMockK
private lateinit var mockViewModel: MyViewModel
@Test
helloIsDisplayed() {
every { mockViewModel.someBool } returns true
composeTestRule.setContent {
MyComponent(mockViewModel)
}
composeTestRule.onNodeWithText("Hello").assertExists()
}
@Test
worldIsDisplayed() {
every { mockViewModel.someBool } returns false
composeTestRule.setContent {
MyComponent(mockViewModel)
}
composeTestRule.onNodeWithText("World").assertExists()
}
}

We’re now able to test that the correct text is displayed given a value.

The testing is somewhat limited. But we can test if something is displayed, exists, perform a click, etc. As far as I can tell there is no way to test what parameters were sent into something, like testing the correct url was passed into an AsyncImage for example.

Tests run very quickly (with a short lead in for the first Robolectric test maybe 10 seconds)

A list of matchers and assertions can be found here https://developer.android.com/jetpack/compose/testing-cheatsheet

--

--