/ Article

Unit Testing React Native with Mocha Chai Sinon Enzyme and Istanbul

Unit testing is an integral part of producing reliable software that meets all customer requirements when working in an agile way. Unit tests allow us to rapidly develop new functionality and add any changes without breaking existing functionality. It gives a developer the comfort that everything is still working as intended.

With “React” and especially with “React Native”, which is always changing and growing, we depend heavily on unit tests to make sense of it all. For this reason, we hope that this short guide on how to unit test React Native applications will allow you to ensure that your application code is kept stable and up to date within a rapidly changing environment.

We will cover unit testing and code coverage as well as writing an example unit test.

In 6 easy steps :

  • Step 1 : Setting up a react-native project.
  • Step 2 : Install unit testing utilities and code coverage tools.
  • Step 3 : Install mocks with which we test react-native components.
  • Step 4 : Configuring all the utilities and tools.
  • Step 5 : Example unit test.
  • Step 6 : Running your unit tests.

/ Step 1

If you already have a react native project you can skip to Step 2.

You can set up a React Native application by following the guide on https://facebook.github.io/react-native/releases/next/docs/getting-started.html which will take you through setting up React Native.

/ Step 2

NB: Make sure to version lock react to the version that react-native uses for Step 2 and Step 3. Some of the following dependencies might override it with a newer version. You can version lock react by removing the the ^ in your package.json.

Run the following command from inside your project directory:

$ npm install --save-dev mocha chai sinon enzyme istanbul@1.0.0-alpha.2

Mocha : Test framework and runner.
Chai : Assertion library.
Sinon : Utility for mocking and stubbing out code. Also used to set up http mocks.
Enzyme : A utility library used to shallow render React and React Native components.
Istanbul : A code coverage generator that works well with mocha. We explicitly use the @1.0.0-alpha.2 version which supports JSX.

I would recommend reading up on the basics of how each of these libraries should be used and how to use them. You have to be well acquainted with these libraries in order to effectively write unit tests.

/ Step 3

Install the additional dependencies that are required to mock out React Native components. These libraries will act as a good foundation allowing us to test most of the basic React Native components.

Run the following command from inside your project directory: (Replace xxx with the version number of React used by React Native.)

$ npm install --save-dev react-dom@xxx react-native-mock

/ Step 4

Before we start doing any configuration first have a look at your package.json and make sure it is similar to the following :

{
  "name": "test",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "dependencies": {
    "react": "15.0.2",
    "react-native": "^0.26.1"
  },
  "devDependencies": {
     "chai": "^3.5.0",
    "enzyme": "^2.3.0",
    "istanbul": "^1.0.0-alpha.2",
    "mocha": "^2.5.2",
    "react-native-mock": "^0.2.0",
    "react-dom": "15.0.2",
    "sinon": "^1.17.4"
  }
}

We will now set up all the files and scripts used to configure these libraries.

The first configuration file we set up will be used by our test runner and code coverage tools (Moch and Istanbul) to convert JSX into readable Javascript. This is the same configuration React Native uses internally. We have to configure babel which is a javascript transpiler (It comes with React Native) with the following file.

Create a file called babel.rc in your project directory and copy the following snippet into it :

{
  "presets": [ "react-native" ]
}

Next we will set up a javascript file that will run before all our tests to configure any additional dependencies that are needed this will include loading Chai, Sinon and react-native-mock. It will also export some shorthand functions to simplify testing.

Create a folder called test in your project directory and create a file called setup.js and copy the following snippet into it :

"use strict";

var chai = require("chai");
var sinon = require('sinon');

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

global.sinon = sinon;
global.expect = chai.expect;

Next we will set up the Mocha options that will load babel and the setup.js file we defined previously.

Create a file called mocha.opts and copy the following snippet into it :

--require node_modules/babel-core/register
--require test/setup.js

Next we will specify how istanbul has to generate code coverage reports. We will configure Istanbul to do the following :

  1. We use the verbose setting to specify that only the necessary information should be logged to the console.
  2. We specify our application root as ‘./app/’ and exclude all ‘test’ folders to be processed by code coverage since these will contain our tests.
  3. We configure reporting to output html, text (console) and lcov reports.
  4. We set thresholds for the test coverage watermarks which are displayed in RED, AMBER and GREEN.

Create a file called .istanbul.yml and copy the following snippet into it :

verbose: false
instrumentation:
  root: ./app/
  excludes: ["**/__tests__/**"]
  include-all-sources: true
reporting:
  print: summary
  reports:
    - html
    - text
    - lcov
  watermarks:
    statements: [50, 80]
    lines: [50, 80]
    functions: [50, 80]
    branches: [50, 80]   

And finally we will add a test script to our package.json that will execute our unit tests. Add the following line to your package.json under scripts :

"test": "node_modules/.bin/istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts app/**/*.spec.js"

/ Step 5

We will set up a simple dummy component and example unit test specification that we can run in order to determine that our unit tests work and that code coverage reports are generated.

Create a folder called app in your project directory. We will create all our React Native components in this folder as well as our tests.

Create a file called TestComponent.js. This is just a dummy component that we will test and has no major functionality. We recommend that you read up on React and React Native if you want a better understanding.

Copy the following snippet into TestComponent.js :

import React, { Component } from "react";

import {
  View,
  Text
} from 'react-native';

export default class TestComponent extends Component {
  constructor(props) {
    super(props);
  }

  _click() {
    alert();
  }

  render() {
    return (
      <View onPress={this._click.bind(this)}>
        {this.props.items.map((item, index) => (
          <View key={index}>
            <Text>{item.name}</Text>
          </View>
        ))}
      </View>
    );
  }
}

Create a folder next to TestComponent.js called tests.

Create a file called in the “tests” folder called TestComponent.spec.js all our unit test specifications will follow this convention to make it easy to find unit tests and have them next to our components at all times. This will encourage developers to keep unit tests updated and visible to other developers at all times. The following file just runs a few basic unit tests against our TestComponent.js

Copy the following snippet into TestComponent.spec.js :

import React from "react";
import { shallow } from "enzyme";

import TestComponent from "../TestComponent";

describe("<TestComponent />", () => {
  let mockData = [
    { name: "test" },
    { name: "test" }
  ];

  it("should render children when supplied the items prop", () => {
    let wrapper = shallow(<TestComponent items={mockData}/>);

    let items = wrapper.findWhere((component) => {
      return component.props().children === "test";
    });

    expect(items.length).to.equal(2);
  });

  it("should point to the _click function in onPress", () => {
    global.alert = sinon.spy();

    let wrapper = shallow(<TestComponent items={mockData}/>);

    wrapper.simulate('press');

    expect(wrapper.props().onPress.name).to.contain('bound _click');

    describe("<TestComponent/> : _click()", () => {
      it("should trigger an alert if onPress is executed", () => {
        expect(global.alert.calledOnce).to.equal(true);
      });
    });
  });
});

/ Step 6

You can now run your TestComponent unit test using the following command, and view the coverage reports in your console as well as in your project directory under coverage.

$ npm test

You have now successfully added unit tests and code coverage reports to a React Native project. In order to fully utilise unit tests read up on the above mentioned technologies and start using it immediately.

To view this in action have a look at https://github.com/TangentSolutions/UnitTestingReactNative

4 Comments

  • Thank you, this is the first article i have found that has really explained this topic very well.

  • We can easily witch out react-native without anything like proxyquire by using a custom compiler, which just switches out the import.

  • I used this as a guide to set up testing and coverage for a personal project. Thank you.

  • Step 4 - create file .babelrc, not babel.rc

Leave a Comment