Skip to content

Unit Testing

Francois Venter edited this page May 26, 2016 · 11 revisions

Unit Testing React Native

This is a short guide on how to get started with react-native and unit tests.

We face multiple difficulties that are not easy to overcome without some handy tools and utilities. This short guide is intended to guide you through everything that you need in order to set up unit testing with react-native.

Step 1

If you already have a react-native project set up you can continue to Step 2.

Follow the guide on : https://facebook.github.io/react-native/releases/next/docs/getting-started.html

Step 2

NB : Make sure you version lock react to the version that react-native uses. Some of the following dependencies might override it with a newer version. Only add the ^ back to react when updating react-native.

Install the tools that we need in order to do our tests.

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

  1. Mocha : A test framework for javascript.
  2. Chai : An assertion library.
  3. Sinon : A library containing helpers to mock/spy during tests. Also supports http mocking.
  4. Enzyme : A utility library to help us with shallow rendering of react components so that we can test their structure and functionality without the need to render the DOM or in our case the native views.
  5. Istanbul : Used to generate code coverage reports. The version capable of generating coverage reports for JSX at the time of writing is 1.0.0-alpha.2

I would recommend reading up on the basics of how each of these libraries should be used and how to use them.

Step 3

Install the requirements to mock out react-native components. Replace XXX with the version of react used with react-native. i.e. 15.0.2

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

This is a 3rd party library, it may not contain all the desired mocks but will act as a good foundation to start testing your components. Any additional required mocks should be added manually.

Step 4

Create all the necessary configuration files and scripts needed to run our tests.

First

Before we create any configuration files make sure that your package.json looks 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"
  }
}

Add the following files :

mocha.opts

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

This file will ensure that mocha can transform the JSX code as well as set up our testing requirements.

.istanbul.yml

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]    

This file specifies how istanbul should generate reports and what to report on. We exclude all __tests__ folders since our {component}.spec.js files will be stored in these folders so that their related components are easily required. All source files are assumed to be in the ./app directory.

This .istanbul.yml config file will generate and log more than what is necessary and you may change it as you please.

./test/setup.js

"use strict";

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

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

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

This file will import all our requirments for testing including mocks for react-native. If additional mocks are required add them here or require them from this file. Mocks should be added to the test folder.

.babelrc

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

Add the react-native preset to babel, this comes with react-native.

Finally

Edit your package.json to contain the following under scripts :

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

Step 5

A dummy component and test cases, these test cases are merely for demonstration purposes.

Add the following files.

./app/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>
    );
  }
}

./app/__tests__/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

Run your unit tests to ensure that everything is in order.

$ npm test

This will display your test cases in the terminal as well as a summary of your code coverage. It also generates a ./coverage directory containing coverage reports.

[Please let me know if anything is unclear or if some of the steps don't work for you. I will try to keep this updated and flesh it out over time as needed.]