Enhancing Your app’s Testability
Testing is an indispensable part of any project. It enables you to deliver your app with confidence, maintaining a high level of stability and quality. However, tests must be conducted effectively and updated whenever changes occur in your code base.
A typical Android Studio project contains two directories for testing based on their execution environment.
- The test directory contains unit tests, which are faster and local, and can be run on your development machine or a server.
- The androidTest directory should contain tests that are executed on real or virtual devices. These include integration tests, end-to-end tests, and other tests where the Java Virtual Machine (JVM) alone cannot validate your app’s functionality.
Given a good architecture, these tests can cover a significant part of your application. Typically, all logic will be covered by the unit test and the User Interface (UI) or other untestable functions will be covered by the androidTest.
However, there may be a need for additional testing methods for several reasons. For instance, speed is a factor; a large number of androidTests take a significant amount of time to run. In terms of simplicity, you may need to test a UI that is full of components. There’s also the necessity aspect; it can be challenging to test custom views since the tags are not always easy to apply.
These are the reasons why I sought an alternate solution and found the Screenshot test to be incredibly beneficial.
Screenshot testing involves capturing and comparing snapshots of expected output from a component. It is crucial for validating your app’s appearance and functionality, detecting visual issues, and testing the app as users would use it. It is more efficient than writing numerous assert statements.
If you use Jetpack Compose, screenshot testing allows you to compare previews of your components before and after changes. This makes it easy to test for alterations in texts, padding, style, and anything you can see on your screen.
How to start
I started using screenshot testing with Paparazzi, a very smart library that I encourage you to check out. However, I experienced some problems with it, as it is incompatible with Robolectric and can cause problems when updating Gradle if it is not supported by the library.
Following the Now in Android episode #90, I discovered a new way to do screenshot testing with another library called Roborazzi. It is integrated with Robolectric, which allows you to interact with your components inside the tests.
Let’s start developing! I’ll show you how to integrate and use this library in your existing project (or in a new project, as I’ll do here to show you all the steps in detail).
Configure the project
To add Roborazzi to your project, you need to add the following plugin to your root build.gradle file:
plugins { ... id ("io.github.takahirom.roborazzi") version "1.7.0-alpha-1" apply false }
In the module build.gradle:
plugins { ... id("io.github.takahirom.roborazzi") }
android { ... testOptions.unitTests.isIncludeAndroidResources = true }
You also need to add the following dependencies:
testImplementation("androidx.compose.ui:ui-test-junit4-android") testImplementation("org.robolectric:robolectric:4.10.3") testImplementation("io.github.takahirom.roborazzi:roborazzi:1.7.0-alpha-1") testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.7.0-alpha-1") testImplementation("androidx.test.ext:junit-ktx:1.1.5")
Writing Roborazzi tests
Once you have added Roborazzi to your project, you can start writing Roborazzi tests. Here a simple one:
@RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel5) class MainActivityKtTest { @get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>() val directoryName = "MainActivityKtTest" @Test fun testGreeting() { composeTestRule.setContent { MyTheme { Greeting(name = "Test", modifier = Modifier.padding(12.dp)) } } composeTestRule.onRoot().captureRoboImage( filePath = "src/test/screenshots/$directoryName/greeting.png", roborazziOptions = RoborazziOptions( recordOptions = RoborazziOptions.RecordOptions(resizeScale = 0.5) ) ) } }
Job Offers
To take a screenshot of the Compose component, we can use the captureRoboImage() function. This function will take a screenshot of your component and save it to a file.
Running your tests
To run your Roborazzi tests, you can use the following command:
./gradlew recordRoborazziDebug
Following the captureRoboImage() function an image is generate.
This is the golden value, the image that will be used to understand if something is changing.
To demonstrate how to compare the screenshots, I will modify this line of code:
Greeting(name = "Test 2", modifier = Modifier.padding(2.dp))
To verify that your Compose component still looks as expected, you can run the following command:
./gradlew verifyRoborazziDebug
This will compare the current image of your Compose component to the golden image. If the images are different, the test will fail.
In this case we have a failed test. We can locate the comparison image by navigating through the following path: build -> output -> roborazzi. This image will provide a visual representation of the changes that occurred.
You can observe the golden image on the left and the new one on the right, with all the differences displayed in the center.
A notable enhancement involves adding the following lines to the gradle.properties file:
roborazzi.test.compare = true roborazzi.test.verify = true
This configuration will ensure that the verification process is automatically executed every time you run your tests.
Testing rules
Another notable feature is related to testing rules. By adding the following dependency:
testImplementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:1.7.0-alpha-1")
You gain the ability to write tests in the following manner:
@get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>() val directoryName = "MainActivityKtTest" @get:Rule val roborazziRule = RoborazziRule( composeRule = composeTestRule, captureRoot = composeTestRule.onRoot(), options = RoborazziRule.Options( captureType = RoborazziRule.CaptureType.LastImage(), outputDirectoryPath = "src/test/screenshots/$directoryName", outputFileProvider = { description, outputDirectory, fileExtension -> File(outputDirectory, "${description.methodName}.$fileExtension") } )) @Test fun testGreeting() { composeTestRule.setContent { MyTheme { Greeting(name = "Test", modifier = Modifier.padding(12.dp)) } } }
This approach eliminates the need to manually call captureRoboImage() and specify the path each time you run tests.
Additionally, I’d like to mention another feature: the ability to use GIFs for testing animations or interactions. However, I’ve encountered several issues with this feature, so please be careful before incorporating it into your testing workflow.
Conclusions
You’ve seen in this article the basic of screenshot testing and how to integrate and utilize Roborazzi in your projects. This library will help you to increase your coverage and simplify your tests.
I’d love to hear your thoughts on this matter. Please feel free to share your comments below, or if you prefer, you can reach out to me on Twitter or LinkedIn.
Have a great day!
This article was previously published on proandroiddev.com