chantastic / react-testing-patterns

Mostly reasonable patterns for testing React on Rails

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React Testing Patterns

Mostly reasonable patterns for testing React on Rails

Table of Contents

  1. Scope
  2. Constraints
  3. A Dirty-UMD
  4. Mocha vs Jasmine vs Jest
  5. A Mocha Setup
  6. Assertion Libraries
  7. Mock a Doc
  8. A Test Boilerplate
  9. React Shallow Rendering
  10. Watching the File System

Scope

This is how we write tests for React.js on Rails. We've struggled to find the happy path. This is our ongoing attempt to carve out the most direct path to testing React components on a golden-path Rails app. Recommendations here represent a good number of failed attempts. If something seems out of place, it probably is; let us know what you've found.

Constraints

Our approach has the following constraints.

  • Scripts work in the Asset Pipeline
  • Scripts work in a JS-testing framework
  • Module unit tests should work without a DOM
  • Testing envirnoment should be flexible for app/team-specific needs
  • ES2015 syntax support

A Dirty-UMD

Component definitions will need to work in window and as a module. This is ugly but it works.

(function (global) {
  "use strict";

  let React;

  if (typeof module === "object" && module.exports) {
    React = require("react");
  } else {
    React = global.React;
  }

  class MyWidget extends React.Component {
    render () {
      return <div />
    }
  }

  if (typeof module === "object" && module.exports) {
    module.exports = MyWidget;
  } else {
    global.MyWidget = MyWidget;
  }
})(this);

Feel free to DRY this out however feel right to you.

Mocha vs Jasmine vs Jest

Here's how it shook out against our particular constraints.

TL;DR

In 2015, use Mocha. That might change if these Jest issue get resolved:

  • Speedy ES2015 support
  • Jasmine updated to 2.x

Jest

  • Not as flexible as Mocha for app-specific needs
  • Ships with DOM implementation, jsdom = locked to unsuportted 3.x tag
  • Slow. A test suite of only 4 tests and 23 assertions took 10.7 seconds.
  • Locked to Jasmine 1.3

Jasmine

Mocha

  • Flexible for teams
    • Very few opinions about anything
    • Well documented
    • Simple to configure
  • No default DOM implementation
  • Fast. A test suite of only 4 tests and 23 assertions took 1.2 seconds.

Other considerations

Jest has very nice feature for auto-mocking and running tests in parallel. But, for now, it's an order of magnitude slower than Mocha (given our constraints).

A Mocha Setup

Init package.json, if you havent done so:

$ npm init

Install Mocha:

$ npm install mocha --save-dev

Create the test/ directory, if one doesn't exist:

$ mkdir `test/`

*mocha will automatically run tests matching ./test/*.js. ref

Configure the npm test command:

{
  "scripts": {
    "test": "mocha"
  }
}

Now you can run your local version of mocha via the npm test command.

Additionally, you can run the local version of mocha with flags via the node_modules directory:

$ node_modules/.bin/mocha --watch --compilers js:babel/register --recursive --reporter nyan

Add a test/mocha.opts for shared options:

--watch
--compilers js:babel/register
--recursive
--reporter nyan

This configuration will be used in both npm test and node_modules/.bin/mocha

Assertion Libraries

People like what they like.

My $.02. If you don't have a PM that pretends to read specs, write assertions. Just sayin'.

node/assert

$ # you already have node/assert. lucky you
import assert from "assert";

assert(result.type, "div");

API

expect.js

$ npm install expect.js --save-dev
import expect from "expect.js";

expect(result.type).to.be("div");

API

should

$ npm install should --save-dev
import expect from "should";

(result.type).should.be.exactly("div");

API

chai

$ npm install chai --save-dev

Chai has 3 included assertion libraries. Chose your favorite.

import chai from "chai";

const assert = chai.assert;
const expect = chai.expect;
const should = chai.should;

assert.strictEqual(result.type, "div");
expect(result.type).should.equal("div");
result.type.should.equal("div");

API Mock a Doc

One of the criteria is testing without a DOM. That sholud be possible using React shallow rendering but there's a bug.

You can bypass it by mocking document.

// ./test/utils/document.js

if (typeof document === 'undefined') {
  global.document = {};
}

Add this flag to your mocha.opts:

--require test/utils/document.js

Should you want a full fledged DOM, follow this guide by Jake Trent.

A Test Boilerplate

Here's what a standard test looks like.

"use strict";

import assert from "assert";

import React, { addons } from "react/addons";
import AppIcon from "../AppIcon.js.jsx";

let shallowRenderer = React.addons.TestUtils.createRenderer();

describe("MyWidget", () => {
  shallowRenderer.render(<MyWidget />);
  let result = shallowRenderer.getRenderOutput();

  it("renders an div tag as its root element", () => {
    assert.strictEqual(result.type, "div");
  });
});

You can required your assertion library in mocha.opts to avoid requiring in each test.

--require assert

React Shallow Rendering

Where reasonable, use shallow-rendering. Shallow rendering does not Require a DOM.

The ReactTestUtils-test.js file are the best documentation on how shallow rendering works.

Here are the basics:

Create a Renderer

import { addons } from "react/addons";

let shallowRenderer = React.addons.TestUtils.createRenderer();

Render and get Result

shallowRenderer.render(<MyWidget />);
let result = shallowRenderer.getRenderOutput();

Asserting type

assert.strictEqual(result.type, "div");

Assert children

assert.deepEqual(result.props.children, [
  <div>hi!</div>,
  <div>okay, bye.</div>
]);

Assert update

shallowRenderer.render(<InterfacesIcon name="" hoverStyle={{ color: "blue" }} />);
result = shallowRenderer.getRenderOutput();

assert.deepEqual(result.props.style, {});

result.props.onMouseEnter();
let mouseEnterResult = shallowRenderer.getRenderOutput();

assert.deepEqual(mouseEnterResult.props.style, { color: "blue" });

*See Mock a Doc

Watching the File System

mocha --watch is busted. I only ran into trouble when attempting to use sinon.js.

If you use sinon for stubs, spies, and mocks, you're going to need a dedicated fs watcher. Unfortunately, this is much slower than mocha --watch but is has the benefit of, you know, working.

I like nodemon for filesystem watching.

Install

$ npm install nodemon --save-dev

Add Script to package.json

{
  "scripts": {
    "test:watch": "node_modules/.bin/nodemon -w app/assets/javascripts/interfaces/components node_modules/.bin/_mocha"
  }
}

nodemon.json

Configuration can be pulled out into nodemon.json.

{
  "watch": [
    "app/assets/javascripts/interfaces/components",
    "test/assets/javascripts/interfaces/components"
  ]
}

Run script

$ npm run test:watch

note: npm test is first-class npm command. All other scripts must be run with the run prefix.

About

Mostly reasonable patterns for testing React on Rails