I spent some time yesterday setting up testing in a React Native app and it was surprisingly tricky.

I wanted to use Enzyme with it’s really simple API for testing my components and Mocha for running the tests. This setup seems to be fairly popular but none of the guides I found worked well with the latest version of React Native (0.25 as of last edit). In the end what worked for me was cobbled together from some blog posts and a whole bunch of GitHub issues.

Already got testing set up? I’ve wrote another post on how to test code which has dependencies here

The packages we need

To get this set up, these are the packages we’ll install.

Babel for transpiling our javascript

Mocha for running the tests

Chai for our assertions

Sinon for spies, stubs and mocks

Enzyme for nice component tests

To get enzyme to work, we also need react-dom as well as react-native-mock, which gives us an almost fully mocked version of React Native.

npm i --save-dev babel-core babel-preset-react-native babel-preset-airbnb mocha chai chai-enzyme enzyme@2.2 react-native-mock react-dom sinon

There are plenty of other useful packages too, but this is a good foundation.

Edited to set enzyme version to 2.2. I was getting errors on React Native 0.25 using the RN mocks after enzyme 2.3 was released. YMMV.

The tricky bit

Great! We have the packages we need. It doesn’t work yet though.

There’s ES6 code that isn’t being transpiled and we need to make sure the react-native mocks are being used.

To get that working, we can create a simple setup script that will run before the tests. This will ensure babel transpiles our code, the react-native module, and some of our other dependencies. You’ll also need a .babelrc file in the root of the project, like this:

{

"presets": [

"react-native"

]

}

Next, create the setup file. I put this at test/setup.js

import fs from 'fs';

import path from 'path';

import register from 'babel-core/register';

import chai from 'chai';

import chaiEnzyme from 'chai-enzyme'; // Ignore all node_modules except these

const modulesToCompile = [

'react-native',

'apsl-react-native-button',

'react-native-router-flux',

'react-native-tabs',

'react-native-vector-icons',

'react-native-mock',

'react-native-parallax-scroll-view',

'react-native-simple-store'

].map((moduleName) => new RegExp(`/node_modules/${moduleName}`)); const rcPath = path.join(__dirname, '..', '.babelrc');

const source = fs.readFileSync(rcPath).toString();

const config = JSON.parse(source);

config.ignore = function(filename) {

if (!(/\/node_modules\//).test(filename)) {

return false;

} else {

const matches = modulesToCompile.filter((regex) => regex.test(filename));

const shouldIgnore = matches.length === 0;

return shouldIgnore;

}

} register(config); // Setup globals / chai

global.__DEV__ = true;

global.expect = chai.expect;

chai.use(chaiEnzyme()); // Setup mocks

require('react-native-mock/mock');

const React = require('react-native')

React.NavigationExperimental = {

AnimatedView: React.View

};

There’s pretty much 3 parts to this

Make sure babel doesn’t ignore anything outside of node_modules or any of the modules specified in modulesToCompile.

Set up some globals like ‘expect’ so that it doesn’t have to be imported everywhere

Enable react-native-mock (I also mocked a small part of NavigationExperimental here as it’s missing from react-native-mock right now)

You’ll probably need to play around with the modules that are specified in modulesToCompile so that it matches your own needs. If you start getting errors like “unrecognised token”, chances are you’re not transpiling something.

Writing some tests

Finally, we can get to the good stuff. Let’s write some tests! We’ll use this really basic AngryButton component as an example.

NB I’ve put a project with this component in a GitHub repo, if you want to see everything together then check it out here.

class AngryButton extends Component {

render() {

const { text, onPress } = this.props;

return (

<TouchableHighlight style={styles.button} onPress={onPress}>

<Text style={styles.text}>{text.toUpperCase()}</Text>

</TouchableHighlight>

);

}

}

The AngryButton component takes two props; some text that it will make ANGRY, and a function to call when the button is pressed.

Inspiration for our button

To start we’ll just test that it actually renders — if this works then we know we’ve got things set up ok.

import React, { Text, TouchableHighlight } from "react-native";

import { shallow } from "enzyme";

import AngryButton from "../AngryButton"; describe("<AngryButton/>", () => {

it("should render", () => {

const button = shallow(<AngryButton text="bananas" />);

expect(button.contains(TouchableHighlight)).to.equal(true);

expect(button.contains(Text)).to.equal(true);

});

})

Although this is basic stuff, it also shows how simple enzyme is — it makes what the test is doing obvious.

I like my tests to live near what they’re testing, so I have them in a __specs__ folder next to the component but you can put them wherever you want. For this example, it’s at src/components/__specs__/AngryButton.spec.js.

To run the test with mocha, we need to tell it to use babel and to require our setup file before the test is run. After that we just need to tell it where the tests are.

mocha --require test/setup.js --compilers js:babel-core/register src/**/__specs__/*.spec.js

If that worked, you should see the passing test.

Now that we have it working, we can add a couple more test cases — let’s import sinon so that we can test button presses work.

import sinon from "sinon";

and then add the test cases

it("should capitalise text", () => {

const button = shallow(<AngryButton text="bananas" />);

expect(button.find(Text).props().children).to.equal("BANANAS");

}); it("should handle button presses", () => {

const onPress = sinon.spy();

const button = shallow(<AngryButton text="yo" onPress={onPress}/>);

button.simulate('press');

expect(onPress.calledOnce).to.equal(true);

});

Now we’ve validated the text is being uppercased and that the onPress event gets fired when the button is pressed. Again, really simple and readable.

Tidying up

We could leave it there, but let’s tidy things up a little. Mocha lets us take the options we always pass to it and put them in an options file. I’ve put these in test/mocha.opts

--require test/setup.js

--compilers js:babel-core/register

Let’s add a test script to our package.json too

...

"scripts": {

"test": "mocha --opts test/mocha.opts src/**/__specs__/*.spec.js"

}

...

Now to run our tests we just run “npm test”.

You can check out all of this together at the example Github repo. It’s worth looking into each of the packages to get more of a feel for what they can do. It’s also really useful to have a look at how open source React Native packages do their testing — react-native-simple-auth is a good example.

Have fun testing 😄.

We’re working on a React Native eBook — want a copy? sign up for updates on our website.