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:
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.
-
verifyPaparazziDebug
-- Runs tests and verifies against previously-recorded golden values. Failures generate diffs atbuild/paparazzi/failures
. -
recordPaparazziDebug
-- Saves snapshots as golden values to a predefined source-controlled location (defaults tosrc/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.
Top comments (0)