At Add Jam, we're always looking to improve our internal development processes to help improve code quality and ultimately the end user experience of the products we make.
In the last week an area I've been focusing on improving is our React Native testing strategy. As I was upgrading our React Native projects to the latest release of the framework it felt like the perfect opportunity to reassess, refine and improve our testing strategy for React Native apps.
It's important to take time and define a testing strategy that makes sense. What do I mean by this? Well I believe adding tests for the sake of tests isn't necessarily a good use of time. Tests in a software project are actually useful when they actively help the development process and help developers catch bugs, errors and unintended changes. It's for catching unintended changes that I've found that "snapshot testing" is a great way ensure our app's functionality, user experience and flow remain stable.
The Challenge
As our apps grow in complexity, it becomes increasingly important to have a robust testing suite that we can trust and rely on to help catch bugs and regressions early in the development process. At Add Jam we pride ourselves on truly working in an agile manner. We move fast, ship often and aim to get products to market quickly. Having trust in our code allows us to focus our time iterating on the areas that add the most value to our end users.
This process of iteration can easily result in features constantly being added, removed or refactored during not just the release initial process but also during continued development and ongoing product improvement work.
To help us catch unintended changes and ensure only intentional changes make it to production, we've incorporated Jest snapshot testing into our React Native projects.
What is Snapshot Testing?
Jest is a popular JavaScript testing framework that integrates seamlessly with React Native. With one of the most powerful features of Jest being snapshot testing.
With snapshot tests (as the name suggests) you essentially take a "snapshot" of a component's rendered output at a given point in time for a given set of inputs. Future test runs are then compared against these saved snapshots. If anything has changed in the output, the test will fail, alerting you to investigate whether the change was intentional or accidental.
This failure alert is particularly useful when tied to GitHub Actions. When we create pull requests on GitHub our Github Actions will run our snapshot tests and if the snapshot tests fails, the pull request will be marked as not mergeable. This in turn ensures the person reviewing the code (as well as the Pull Request author) are aware of potential issues to resolve before merging. This has several benefits to us:
- improving the 'reviewer' experience making it easier to review pull requests
- Improve code quality
- Reduce the chance of unintended changes entering production
Integrating Snapshot Tests with React Navigation
To start snapshot testing your React Navigation components, you'll need to do a bit of setup:
- Ensure you have Jest, @testing-library/react-native and react-test-renderer installed
- Create a
__tests__
directory and add test files for each screen you want to test. We like to break these down based on the type of route the screen follows, such asauthentication
,onboarding
,home
,profile
etc. - In your test file, import the screen component and use
@testing-library/react-native
to render it - Call the
toMatchSnapshot()
assertion to generate the initial snapshot
Here's a simplified example:
import React from "react"
import "react-native"
import { render, screen, waitFor } from "@testing-library/react-native"
import TestWrapper from "@tests/utils/TestWrapper"
import Home from "app/screens/Home"
it("renders the Home Screen correctly", async () => {
const { toJSON } = render(<HomeScreen />, {
wrapper: TestWrapper,
})
await waitFor(() => {
expect(toJSON()).toBeTruthy()
})
expect(toJSON()).toMatchSnapshot()
})
Low Cost, Low Effort, High Value
By using snapshot tests, you increase the likelihood of catching unintended changes. Which in turn reduces the risk of introducing bugs into the codebase. And as you can see from the example above, it is extremely quick and easy to implement.
The above test alone will ensure any content on the home screen is not changed unintentionally. Which can save you from releasing a new version of your app with missing content, a new feature not yet ready for release, or even catching something as simple as an unintended typo.
If you are making valid changes to the output you can easily update the snapshot. It's a simple case of adding -- -u
when running the test to update your snapshot for the new expected output. Note this should be done sparingly and only when you are actually making intentional changes to the output.
This might seem obvious. But we have seen a number of posts online from developers who use this argument in their CI/CD pipelines by default. Which as you hopefully suspect, is a recipe for disaster and defeats the purpose of snapshot testing.
Should I use them?
While snapshot testing is a great way to catch unintended changes It's not the sole approach to testing a React Native app. Instead you should use Snapshot testing in conjunction with other testing strategies. For example Snapshots are not a replacement for unit tests, but rather a complement to them.
Given how little effort and work it takes to implement Snapshot tests (even if just on a basic level or for some foundational components or screens) we think it is a worthwhile addition to any production React Native app.
Need help testing your React Native app?
Looking for more insight on improving your React Native projects? Then be sure to have a look at our other React Native blog posts
If you're ever in Glasgow or the surrounding area and want to talk web or mobile development, then get in touch and we would love to arrange a coffee.