Testing with Jest
This guide explains how to set up Jest in your project, write a unit test, write a snapshot test, and common problems people encounter when using Jest in React Native.
Jest is the most widely used JavaScript unit testing framework, so you may already be familiar with it.
The first thing we'll want to do is to install jest-expo
, it's a Jest preset that mocks out the native side of the Expo SDK and handles most of configurations for you.
To install the compatible version of jest-expo
for your project, run: expo install jest-expo jest
Then, we need to add/update package.json to include:
"scripts": {
...
"test": "jest"
},
"jest": {
"preset": "jest-expo"
}
Now let's add react-test-renderer
to our project. Pick a version that is compatible with the React version used by your project. For example, if you use React 17.x then you should install react-test-renderer@17
:
yarn add react-test-renderer@17 --dev
or npm i react-test-renderer@17 --save-dev
That's it! Now we can start writing Jest tests!
Note:
react-native-testing-library is a library built on top of react-test-renderer that could be helpful in your workflow, but we won't cover it in this guide.
Jest comes with a lot of configuration options, for more details read
Configuring Jest.
We would like to point out
transformIgnorePatterns
. Below is a great starting point to make sure any modules you may be using within
/node_modules/
are transpiled when running jest. This should cover the majority of your needs but you can always add to this pattern list as you see fit.
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
]
}
We are going to write a simple test for
App.js by creating its own test file:
App.test.js. Jest will identify this as a test and include it in the tests queue. There are other ways to
structure your tests, but we will cover that later in this guide.
Our test will be the expected state of the <App />
to have one child element:
import React from 'react';
import renderer from 'react-test-renderer';
import App from './App';
describe('<App />', () => {
it('has 1 child', () => {
const tree = renderer.create(<App />).toJSON();
expect(tree.children.length).toBe(1);
});
});
What is a snapshot test, and why is it useful? Snapshot tests are used to make sure the UI stays consistent, especially when a project is working with global styles that are potentially shared across components. Read more about it on Jest's site
snapshot testing.
Now let's add a snapshot test for App.js, add the following within the describe()
:
it('renders correctly', () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
Now run yarn test
or npm run test
, if everything goes well, you should see a snapshot created and two tests passed!
This was a very simple test, for more information, take a look at the following links:
Running tests are cool and all, but wouldn't you like to see total code coverage in your Expo projects?! Maybe even see it in an HTML format?! This section is for you!
Let's head back to package.json and add the following:
"jest": {
...
"collectCoverage": true,
"collectCoverageFrom": [
"**/*.{js,jsx}",
"!**/coverage/**",
"!**/node_modules/**",
"!**/babel.config.js",
"!**/jest.setup.js"
]
}
The above additions let Jest know to collect coverage of all .js & .jsx file types and not within /coverage, /node_modules/ and our 2 project config files (add/remove more exclusions to this list to match your needs).
Now run the test again, you should see /coverage in your app directory! Find the index.html file within and double click to open it up in a browser. Not only do we have reporting in our CLI, we also have an HTML version of our code coverage, pretty cool stuff!
Standards Note: You can do what you want, but usually we wouldn't upload this html reporting to git; so add coverage/\*\*/*
as a line in .gitignore
to prevent this directory from being tracked.
As promised, let's talk about how to set up the tests. Right now we have a single .test.js
in the root of our Expo project, this can get messy quickly. The easiest way is to create a __tests__
directory (anywhere you'd like) and put all the tests there. For example see below:
__tests__/
├─ components/
│ └─ button.test.js
├─ navigation/
│ └─ mainstack.test.js
└─ screens/
└─ home.test.js
src/
├─ components/
│ └─ button.js
├─ navigation/
│ └─ mainstack.js
└─ screens/
└─ home.js
This could cause a lot of long import paths though (../../src/components/button
), so another option would be to have multiple __tests__
directories:
src/
├─ components/
├─ __tests__/
│ │ └─ button.test.js
│ └─ button.js
...
So if we move __tests__
within components
, the button.test.js import of <Button />
would now be: ../button
, that's a lot better!
Another option for test/file structure:
src/
├─ components/
│ ├─ button.js
│ ├─ button.style.js
│ └─ button.test.js
...
At the end of the day, it's all about preferences and up to you to know how you'd like to structure things, but we wanted to share a few options to consider.
This is optional, but wanted to talk about different Jest test flows. Currently we run the test and that's it, but take a look at the
CLI Options. Below are a few script setups to try:
"scripts": {
...
"test": "jest --watch --coverage=false --changedSince=origin/master",
"testDebug": "jest -o --watch --coverage=false",
"testFinal": "jest",
"updateSnapshots": "jest -u --coverage=false"
}