Blog My Happy PlaceGemma Lara Savill

About UI testing in general and the new Compose Preview Screenshot Testing

By Gemma Lara Savill
Published at June 1, 2024

About

Google made an exciting announcement in Google I/O 2024 regarding Android UI testing. They presented an alpha version of a new UI testing framework that will allow us to test the look of our UI using our Compose Previews!

No need to write tests or assertions. It will just take a snapshot of your existing Jetpack Compose Previews and compare them to baseline references. More about this later.

Clean architecture separates concerns, making testing easier. The Presentation layer (UI/ViewModels) is decoupled from the data layer, allowing us to focus on specific functionalities during tests. With this separation in mind, let's explore the different testing approaches for our UI in clean architecture.

What can we test in the Presentation layer?

1. Testing logic

Ideally, a separate class (often called a ViewModel) acts as the brain behind the UI, managing the data it needs to display and handling user interactions. They should manage the UI data (state) and handle the events that happen in the UI.

ViewModels are ideal for unit testing due to their isolated nature.

ViewModels can also be part of your Integration tests. While unit tests focus on ViewModels in isolation, integration tests can involve ViewModels alongside repositories or data sources to simulate data flow. This would include end-to-end tests, where you can test the whole flow of information in your app, from a Mock data source all the way up to the ViewModel.

2. Testing user actions

Sometimes your UI will look different after a user interacts with it. For example, a toggle switch can show/hide a component on your screen.

To test this type of logic, you can use Android Instrumented Tests that will run on a device or an emulator. There are a few types of test frameworks you can choose from, like the Compose official Test API.

I prefer to use the Robolectric testing framework as it enables you to run UI test without an Android device, this makes your tests faster. Specially if you are using a testing pipeline in your CI setup. Remember that in CI, faster can also mean reducing costs.

Here you can visit my GitHub project Robolectric example testing UI actions as a unit test.

In this project, I test that a text appears on-screen after a toggle switch is activated.

 @Test
  fun `when switch is on then text appears`() {

   composeTestRule.setContent {
    Surface {
     val myViewModel: MyViewModel = viewModel()
     val formState = myViewModel.formState.collectAsState().value
     SimpleForm(formState, myViewModel::onEvent)
    }
   }
   // text is not visible on screen
   composeTestRule.onNodeWithTag("FormActiveText").assertDoesNotExist()
   // switch is toggled to on
   composeTestRule.onNodeWithTag("SimpleFormSwitch").performClick()
   // text is displayed on screen
   composeTestRule.onNodeWithTag("FormActiveText").assertIsDisplayed()
  }

The idea here is to identify your UI components by attaching different tags or semantic names to your nodes. You can then use these tags to test different scenarios and actions. Another way to identify UI components is by using their semantics. These are attributes that describe the functionality of a composable (e.g., button, text field).

These tests, along with well-defined semantics, will help you effectively detect issues related to user interactions and UI updates within your Compose applications.

3. Testing UI look

Ensuring a consistent visual experience across different devices and scenarios is crucial for a polished app. Here's where screenshot tests, also known as snapshot tests in other frameworks, come into play.

This is where this new framework, Compose Preview Screenshot Testing, that I mentioned earlier will come in handy.

The idea is that the system captures an image of what your UI looks like, and when you run your tests, it will compare what your actual UI looks like to the previously captured picture.

So if a change you have made to your UI code now makes it look cut off at the edge of the screen, or a button is in the wrong state (colour), it will be detected.

This becomes extra handy if we use @Preview parameters, such as uiMode or fontScale, to help you scale your tests, simulating different device sizes or settings.

What makes this new framework sound so great is that it will be very low overhead to set up, as it will use your Compose UI Previews as the test sources!

So no need to write any test cases, all we will need to do is place the Previews we want to be checked, and then run our tests with a Gradle task.

To create your the snapshots of your UI, you will only need to run

./gradlew updateDebugScreenshotTest

This will create a folder with .png images of your Compose Previews, named with a concatenation of the fully-qualified name of the test function and a hash of the preview parameters. So every time you are happy with any changes made to your UI, you will need to run this command to update the images.

To check your UI against the snapshots, we will run

./gradlew validateDebugScreenshotTest

It is still early days for this official Compose Preview Screenshot Testing framework, as it is still an alpha release. For the moment, it needs a minimum of Android Gradle 8.5.0-beta01 and Kotlin 1.9.20 to run.

Conclusion

The announcement of Compose Preview Screenshot Testing at Google I/O 2024 is a significant step forward for streamlining UI testing in Compose.

By using existing previews and automating the snapshot comparisons, this new framework promises to reduce set up time and effort.

If you want to read about Jetpack Compose Previews and what Preview annotations you can use, read my post, "See it Before You Build It: Mastering Jetpack Compose Previews".

Looking ahead, the future of UI testing in Compose appears bright.

With tools like Compose Preview Screenshot Testing simplifying the process, we can dedicate more time to crafting beautiful and functional UIs that provide an exceptional user experience.