An Introduction to Paparazzi for Snapshot Testing

Using Jetpack Compose

An Introduction to Paparazzi for Snapshot Testing

TL;DR: Snapshot testing with Paparazzi allows you to ensure your Jetpack Compose components behave as expected, without needing an emulator. This article covers setting up Paparazzi, creating a simple Tag composable, and using Paparazzi to create, verify, and update snapshot tests for your Android app.

What is Snapshot Testing?

Snapshot testing allows you to ensure that your output continues to behave as expected. This is useful because as you revisit your code, some changes might cause something to break.

When writing snapshot tests for Jetpack Compose components, you first need to have your code in a working state. Then generate a snapshot of its expected output given certain data. The snapshot tests are committed alongside the component.

Paparazzi is an Android library you can use to create those snapshots for your apps in a simple and friendly way and compare them. One of the advantages this library has over its competitor Shot is that it doesn't need an emulator to run the tests, it just runs in the unit test stage which is much faster.

Set up

Let's begin the setup process for our first test! To get started, we need to make some changes to the root build.gradle file. First, we'll add the necessary dependency for Paparazzi and apply the plugin. The latest version available at the time of writing is 1.3.1.

The build.gradle file should look like this after adding the dependency:

buildscript {
    // ... Repositories and other settings
    dependencies {
        classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.3.1'
        // ... Other dependencies
    }
}

// ... The rest of your build.gradle content

apply plugin: 'app.cash.paparazzi'

Next, in your Android library or app module, we need to include the Paparazzi plugin in the plugins block within build.gradle:

plugins {
    // Other plugin ids
    id 'app.cash.paparazzi'
}

After making these changes, perform a Gradle sync to complete the setup process. Now, we are all set to create our first snapshot using Paparazzi!

Creating the first snapshot test

Let's create a simple Tag composable of which we can make a snapshot later on. Normally a component like this would also have support for click interactions and accessibility. For brevity, we will leave these out in this example.

@Composable
fun Tag(
    name: String,
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor)
) {
    Text(
        text = "#$name".toLowerCase(Locale.current),
        modifier = modifier
            .background(
                color = backgroundColor,
                shape = MaterialTheme.shapes.small
            )
            .padding(horizontal = 4.dp, vertical = 2.dp),
        color = contentColor,
        fontSize = 12.sp
    )
}

This will end up in a component looking like this:

A purple tag with the word tag on it

Next, let's create the snapshot test. Create a new test and make sure to place it inside the unitTest folder instead of androidTest.

In the TagTest class, add the Paparazzi rule, use rendering mode SHRINK here if you only want to capture the component instead of the whole screen of the device.

@get:Rule
val paparazzi = Paparazzi(
    deviceConfig = DeviceConfig.NEXUS_5.copy(softButtons = false),
    renderingMode = SessionParams.RenderingMode.SHRINK
)

Create a test to render the component like so;

@Test
fun testTag() {
    paparazzi.snapshot("Tag") {
        MaterialTheme {
            Tag(name = "Snapshot-Test")
        }
    }
}

Because we've added Paparazzi to the Gradle files already, there are now 2 additional tasks that you can run.

TaskDescription
verifyPaparazziDebugRuns tests and verifies against previously-recorded golden values. Failures generate diffs at build/paparazzi/failures.
recordPaparazziDebugSaves snapshots as golden values to a predefined source-controlled location (defaults to src/test/snapshots).

Because we haven't created the golden values yet, running the verify task now will result in a failed test.

$ ./gradlew :app:verifyPaparazziDebug

> Task :app:testReleaseUnitTest

com.appsoluut.paparazzi.tag.TagTest > testTag FAILED
    java.lang.AssertionError at TagTest.kt:19

* What went wrong:
Execution failed for task ':app:testReleaseUnitTest'.
> There were failing tests. See the report at: file://android/paparazzi/app/build/reports/tests/testReleaseUnitTest/index.html

Creating the golden values is simple. Look at the tasks table above and see you only need to run the recordPaparazziDebug task to start recording. If it doesn't already exists, a new snapshots folder will be created in the src/test directory. This will contain our snapshot and should look something like this.

If you would run the verifyPaparazziDebug task again, all tests should report successfully. But let's make it more interesting and break the snapshot! Remove the .toLowerCase(Locale.current) part from the Tag composable so we have minimal changes. Run the verifyPaparazziDebug task again and see what happens.

> Task :app:testDebugUnitTest FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:testDebugUnitTest'.
> There were failing tests. See the report at: file://android/paparazzi/app/build/reports/tests/testDebugUnitTest/index.html

Unsurprisingly the test failed. You can either open up the HTML report and get a written report of what was going wrong, but that one will only tell you the percentage difference or if the size has changed of the snapshot.

More interesting is to see the actual difference in the snapshot. The tasks will now have created 2 output files which can be found in the build/paparazzi/outputs directory. The delta variant will show the expected, difference and actual snapshot in one handy image.

It's directly clear from the above image what the problem is. If this was expected to change, you can just run the recordPaparazziDebug task again to update the golden values.

Final words

I hope this article helped you get a basic understanding of snapshot testing and inspired you to add them to your project. The next part in this series will be adding the tests to be part of your CI/CD setup.

Did you find this article valuable?

Support Random Thoughts by becoming a sponsor. Any amount is appreciated!