Learning to test React Native with Jest — part 1

It’s a snap!(shot)

When we transitioned our mobile app from native code to React Native, testing was the last thing on our mind. Priority one was recreating our application from scratch using React Native — and once we released it, more and more features required implementing, and before we knew it, we had thousands of lines of code …and zero tests. We knew it’d be good practice to write tests, but finding the motivation to write them was an entirely different beast.

Fortunately, once we got started, we found testing with Jest to be fairly painless. My goal is to make it even more painless (that’s a good thing) for you by explaining everything I’ve learned so far.

Adding Jest to our project

In React Native versions 0.38 and greater, Jest is included when creating a project via react-native init, so the following setup may be already done for you. Same goes if you’ve read the documentation from Jest on setting up testing with React Native

1. Installing packages

For our initial setup, we installed three libraries: jest, babel-jest, and react-test-renderer. Be sure to install these as devDependencies using either npm i --save-dev or yarn add -dev.

2. Just a few more lines before we can get testing…

// Add this to your package.json
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native"
}
// Add this to your .babelrc
{
"presets": ["react-native"]
}

Now to run our tests, we simply type npm test on our command line.

3. Creating our first test

Our first test was pretty humble, creating a snapshot of the render output for our Login component.

Why snapshots? Here’s a great article on the value of snapshots, so I won’t go into it too much. Basically, we want to ensure that every time we run our test the output of our test render matches what it was previously (or update those snapshots when they change as expected).

Doing more with Snapshots (enter: Enzyme)

As we started writing more tests, we quickly found for our more complex components the react-test-renderer was just not getting it done, so we transitioned to utilizing an awesome library from the folks at AirbnbEng: Enzyme.

Our setup for Enzyme was straightforward: installing Enzyme dependencies react-dom and react-addons-test-utils, then the enzyme and jest-serializer-enzyme packages, and adding the serializer to our package.json file:

"jest": {
"preset": "react-native",
"snapshotSerializers": [
"./node_modules/jest-serializer-enzyme"
]
},

Enzyme allows for direct manipulation of the props and state of our components so we can create snapshots for multiple renders of the same component. Here’s a short example:

Here our component ReassignLocationMenu takes a prop count that determines how many of an item we are reassigning. This test will create two separate snapshots, representing each case (when count is 1 or 2).

In our actual component, the difference may be trivial (a few words become pluralized if count is greater than one), but with our previous test rendering method we weren’t able to test both cases. Enzyme allows you do that and much more, so be sure to explore the docs for all the goodies.

Test rendering connected components

Our application utilizes react-redux to store much of our local information, and most of our components are wrapped in a connect statement like this:

export default connect(mapStateToProps)(CameraSettings);

Unfortunately, when we attempt to create a test render using shallow(<CameraSettings />), we get an error: Invariant Violation: Could not find ‘store’ in either the context or props of ‘Connect(CameraSettings)’. There must be a better way...

An okay way… (avoid the state)

Early on, we worked around this issue by simply adding an export for the component class and importing it with curly brackets in our test:

// components/menu/CameraSettings.js
export class CameraSettings extends Component { ... }
// __tests__/components/menu/CameraSettings.test.js
import { CameraSettings } from 'components/menu/CameraSettings';

But, this got a little cumbersome due to the need to add all of our props from mapStateToProps manually, creating a fairly non-authentic version of our component in the shallow render of our test.

Further, if we manipulated the state in any way with logic within mapStateToProps (prior to returning to props), we also needed to copy that logic into our test (or in the very least, the resulting value of the logic). This can easily lead to mistakes and also unnecessarily duplicated code.

The better way (mock the state!)

Instead of manually adding props, it feels much more natural to mock the state so that we can avoid duplicating code, plus we have the the added bonus of testing our mapStateToProps function at the same time.

Having a mocked state allows us to test the functionality of mapStateToProps rather than duplicating its logic in our test file.

To mock our state, we utilize the redux-mock-store library.

What’s going on here?

  1. Our CameraSettings component renders toggle switches that represent user preferences for ‘save photos locally’ and ‘open to camera’. It pulls these values from the state to render the switches either on or off.
  2. In order to put those in a mock store our test can use, we first create our initialState object (providing only the values required by our component’s mapStateToProps function), then pass that object into the mockStore function from redux-mock-store.
  3. Next, we pass the mocked store to our connected component adding a second argument to the shallow function: 
    { context: { store: mockStore() } }.
  4. Finally, to render the snapshot of our connected component we use wrapper.dive() to access the actual render output of our component for the snapshot (otherwise wrapper will represent the connect function).

Note: not shown above, we can utilize the wrapper.setContext() function from Enzyme to alter the state and test render based on various situations that our state may be in.

We’ve only dipped our toes in the water so far

This is the first of many posts detailing lessons I’ve learned implementing Jest testing on our React Native app. I hope you might find them helpful, I spent many hours spinning my wheels on things that seem simple now. Looking back, I wished I’d had a simple walkthrough that would make it easier to get things rolling.

Looking ahead, here are some topics I plan to cover in subsequent posts:

  • Running jest command line without npm
  • Understanding test coverage reports
  • Mocking responses from API requests for testing
  • Testing component functions using spies
  • Creating a setup file with global mocks
  • Best practices when mocking libraries
  • Testing functionality for touchable elements (TouchableHighlights, Switches, etc.)

More in this series:

Part 2Part 3Part 4

Bonus! Check out our React Native application here