Snapshots come into play
In my journey with React, I have been always testing and encouraging people who surround me to test their apps. I have always used Jest for it. But let’s not talk about it again and let’s focus on React Native. Wouldn’t it be nice to use the same framework to test our amazing mobile apps? Furthermore… Wouldn’t it be nice to reuse some tests if we are reusing logic between our web app and our mobile app?
I will try to answer those questions in the following series. In this first article, I will focus on testing React Native components and the pros and cons of using the new Snapshot system provided by Jest vs. shallow rendering comparison (test utils and Enzyme).
Why the new Jest?
Before you ask me why I am calling it the new Jest, please check out the updates notes from Jest 14 and Jest 15. It is not only me who says that Jest has a total new face compared to 1 year ago… It is also his number one core contributor:
In this series we will focus on one of the new Jest features: Snapshot testing.
Jest can capture snapshots of React trees or other serializable values to write tests quickly and it provides a seamless update experience.
Let’s begin to prove the above quote!
Set up Jest in a React Native project
Trivial, seriously. Just follow the docs:
DONE. Try it out with:
Time to test components
The example repo for this article renders a list of Github repositories. You can read several test examples in there but for the purpose this article, let’s focus on testing a simple component called RepoItem:
A full file includes the props definition and styles.
This component has an interesting prop called isSelected. When isSelected = true, then the item renders with a green background.
The problems of Shallow Rendering comparison
Have you ever used Shallow rendering from react-addons-test-utils or Enzyme? They are pretty similar, Enzyme got popular cause it has a nicer API which also allows you to find elements in your React trees. Let’s choose this one and try to test a React Native component using it.
Enzyme
First problems when trying to use the shallow function from Enzyme (forget about mount in react-native):
react-addons-test-utils is an implicit dependency in order to support react@0.13–14. Please add the appropriate version to your devDependencies. See https://github.com/airbnb/enzyme#installation
react-dom is an implicit dependency in order to support react@0.13–14. Please add the appropriate version to your devDependencies. See https://github.com/airbnb/enzyme#installation
npm install react-dom react-addons-test-util --save-dev
So we actually need to install react-dom and react-addons-test-util to our devDependencies for Enzyme to work… Ouch!:
Ok, let’s go and try to do the first full shallow comparison:
The onPress function will make this test to fail.
Ok, actually, this first example won’t work. See the onPress property? The first problem that we have here is the way it’s defined in the component:
Both functions won’t be the same (we are creating a new function instance) thus the comparison will fail. We could make some workarounds here but… Let’s be lazy and skip the TouchableHighlightpart and assume that nobody will break it:
This will work but…
Don’t you feel we are reimplementing the render() again? Any prop you add/remove or any change you make… You need to come back here and do the same.
All right, now we want to test the component when the prop isSelected is set to true:
Copy-paste-copy-paste…
Since recreating the bigger picture gets so repetitive, developers tend to test only the dynamic parts, see:
Yes, you can create a function that gives you the shallow component and accepts the isSelected prop
This seems better, right? Let’s continue…
Assume that at some point on this project, our client decides that when you click a list item, a part from the selected background color, we also want to show more information, specifically, the repo description. When isSelected = true, the item renders with a green background and shows the repo description
With this change, two things will happen:
- The test that checks all the render output will fail: We will need to go back and reimplement it
- The test that checks only the dynamic part will pass anyway (Oops! is that good?)
In the first case, we need to go back and copy-paste this line into it’s corresponded place:
When isSelected prop is true
In the second case, we need to remember to go back to the test and add the new cases:
New cases
Do you see the problems? The way I see it… The bigger picture (full render) is complicated to create, tedious to maintain and very repetitive (copy-paste). The second approach (using find, contains…) is unstable: We need to remember to implement all the cases since the new ones won’t break those tests.
Check the full RepoItem-Shallow-test.js file for a bigger picture.
Shallow rendering summary
Pros
- Powerful selector api (find, contains, etc)
- It is actually working in React Native (shallow part)
Cons
- It feels you are repeating yourself all the time, thus, people will usually tend to test only specific parts of the output (render) instead of the big picture
- Copy-paste from the component to the test
- Difficult to maintain
- Go try convincing people to write them
Snapshots to the rescue
Jest 14 introduced Snapshots testing: Check out why they are so great:
No flakiness: Because tests are run in a command line runner instead of a real browser or on a real phone, the test runner doesn’t have to wait for builds, spawn browsers, load a page and drive the UI to get a component into the expected state which tends to be flaky and the test results become noisy.
Fast iteration speed: Engineers want to get results in less than a second rather than waiting for minutes or even hours. If tests don’t run quickly like in most end-to-end frameworks, engineers don’t run them at all or don’t bother writing them in the first place.
Debugging: It’s easy to step into the code of an integration test in JS instead of trying to recreate the screenshot test scenario and debugging what happened in the visual diff.
After the read, let’s try it out and convince ourselves.
We can write a Snapshot test for our first RepoItem (when isSelected is set to false):
Check the result at: RepoItem-Snapshot-test.snap. You will find that it does not use the background color property (as expected) because isSelected is false:
Now, let’s test the output when isSelected is set to true (it needs to render a green background). Since our component is actually a pure function, this becomes trivial:
And we can find the expected part inside the resulted snapshot RepoItem-Snapshot-test.snap:
Great! Now, remember that our client came and asked us to implement the expanded part to show the repo description (a part from the background color). Let’s run the tests.
Oops! A test has failed.
Let’s check why. At developing time, Jest will show us very useful error outputs (you can use the watch argument to see them live). Since this change is expected, we can run:
And that will update our snapshot for free. Check it out:
Boom! Our test failed, we inspected it, the failure was expected and we re-generated the snapshot. We did not have to modify our test. Moreover, this snapshot difference will be shown in our next Pull Request.
And that is all, seriously. Our render output is fully tested for both cases and it is capable to react to changes giving us nice error outputs and an easy way to fix them. Quiet neat, right?
Can I test components with internal state?
Before you ask, yes, you can. Check this example in the Jest repo: Link.react-test.js. Of course, if your component has internal state (side effects), it will get much more complicated to test. See:
I am missing a selector api like Enzyme
Especially if your component has state and you need to trigger some event, it can be very tedious to find the node inside the tree. However, this question has been already asked at Jest issues and it has an official answer:
We are working on a selector API for the test renderer which will make this possible through an official API — Christoph Pojer
Snapshots summary
Pros
- Easy to write
- Don’t repeat yourself (DRY)
- Compatible with React Native: Check RepoList-Snapshot-test.js and its snapshot which renders a ListView with its items (try to do that without snapshots if you can)
- Error messages on develop time (you can use —watch t00)
- More goodies to come!
Cons
- Missing a selector API like Enzyme (but you read above that Jest community is working on it)
- Snapshots can be huge and sometimes it might be tedious to find the part you are looking for (lot of noise). In contrast, pull requests will highlight any changes for you. Update 27 Jul 2017: We created snapshot-diff to overcome this issue. Third part of this article is coming soon.
- I am missing a comparison-tool-like between Snapshots. In my example, I manually need to look the differences between the Snapshot with isSelected set to false vs. isSelected set to true
What is the true value of Snapshots?
I want to wrap up stressing the pull request part again. From Kent C. Dodds:
I could not agree more: Code reviews shine using snapshots. But, be aware then that for the Snapshot concept to work you need to:
Which means:
You need to make sure that the __snapshots__ folders are reviewed in your PRs
Let me add yet another value: Don’t repeat yourself. This has been demonstrated (I hope!) here when we do not have to re-rewrite our component output. But if you want to go further on that, check out the next article of this series.
And if your team could use help from senior developers, check out our React Native development services. We’d love to talk and find out how we can take your React Native project to the next level together.