React Testing

Workflow and strategies

Who am I

Vladimir Novick

Front End Developer & Architect

mail: vnovick@gmail.com

twitter: @VladimirNovick

github: vnovick

facebook: vnovickdev

React

Why bother?

Why TDD?

Eliminate fear of change

Agenda

  • Setup your test environment

  • Strategies to test React Component

  • Simulating events

  • Overriding module inner dependencies

  • Flux & Redux testing strategies

  • TDD Workflow

Assumptions

  • ES6 Basic Syntax

  • Client Side testing framework (mocha/jasmine)

  • Karma

  • Module bundler (webpack/browserify)

The Setup

webpack + mocha + karma

var webpackConfig = require('./frontend/webpack.config.js');
var path = require("path");
module.exports = function(config) {
  config.set({
    basePath: __dirname,
    resolve: {
      root: [
        path.join(__dirname, 'frontend'),
      ],
    },
    frameworks: ['mocha-debug', 'mocha'],
    files: [
      'frontend/scripts/specs/test.js'
    ],
    exclude: [
    ],
    plugins: [
          'karma-chrome-launcher',
          'karma-mocha',
          'karma-sourcemap-loader',
          'karma-webpack',
          'karma-mocha-debug'
    ],
    preprocessors: {
        'frontend/scripts/specs/test.js': [ 'webpack', 'sourcemap' ]
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    webpack: webpackConfig
  });
};

Assertion libs

Advised by Rackt team:  https://github.com/mjackson/expect

import React from 'react';
import expect from 'expect';
import expectJSX from 'expect-jsx';

expect.extend(expectJSX);

class TestComponent extends React.Component {}

describe('expect-jsx', () => {
  it('works', () => {
    expect(<div />).toEqualJSX(<div />);
    // ok

    expect(<div a="1" b="2" />).toEqualJSX(<div />);
    // Error: Expected '<div\n  a="1"\n  b="2"\n/>' to equal '<div />'

    expect(<span />).toNotEqualJSX(<div/>);
    // ok

    expect(<div><TestComponent /></div>).toIncludeJSX(<TestComponent />);
    // ok
  });
});

is extended with expect-jsx

 

Testing React Component

There are different techniques to test React Component with react-addons-test-utils

React TestUtils

Provides set of useful functions to check if React elements are rendered correctly

import { renderIntoDocument, isCompositeComponentWithType, isElementOfType } from 'react-addons-test-utils';
import { assert } from 'chai';

var HelloMessage = React.createClass({
  render: function() {
    return <div><Title text={this.props.name}/></div>;
  }
});

const Title = ({ text }) => (<h1 className="title">{text}</h1>)

describe('Hello Message', () => {
    it('Hello Message renders as composite component of type HelloMessage, () => {
        const HelloComponent = renderIntoDocument(<HelloMessage name="testName"/>);
        assert.equal(isCompositeComponentWithType(HelloComponent, HelloMessage), true)
    });
    it('Hello Message instance is created properly, () => {
        assert.equal(isElementOfType(<HelloMessage/>, HelloMessage), true)
    });
});
  • renderIntoDocument 

  • mockComponent

  • isElement 

  • isElementOfType

  • isCompositeComponent

  • scryRenderedDOMComponentsWithClass

  • findRenderedDOMComponentWithClass

Useful Methods

Simulating events

React test utils provide Simulate method to simulate every event React understands

import { Simulate, findRenderedDOMComponentWithClass, renderIntoDocument } from 'react-addons-test-utils'
 
let HelloMessage = React.createClass({ 
  render: function() {
    return <div>
      <Title clickCallback={()=>{ console.log("Click")}}/>
    </div>;
  }
});

let Title = ({clickCallback}) => <div className="titleComponent" 
                                      onClick={clickCallback}>
                                        Title with click callback
                                  </div>

let helloComponent = renderIntoDocument(<HelloMessage/>);
Simulate.click(
    findRenderedDOMComponentWithClass(helloComponent, 'titleComponent')
);

Shallow Rendering

Shallow rendering is our current recommendation.

It allows you to instantiate a component and get the result of its render function (React Element object).

At that point you can inspect its type and props to verify that the correct result was rendered

 

Ben Alpert - React team

Asserting render output

Shallow Renderer will render only one level deep meaning you don't need to worry about behavior of child components

import { createRenderer } from 'react-addons-test-utils';
import { assert } from 'chai';

var HelloMessage = React.createClass({
  render: function() {
    return <div><Title text={this.props.name}/></div>;
  }
});

const Title = ({ text }) => (<h1 className="title">{text}</h1>)

describe('Hello Message - check for children type', () => {
    it('Hello Message renders children of type Title, () => {
        const shallowRenderer = createRenderer();
        shallowRenderer.render(<HelloMessage name="Vladimir"/>);
        assert.equal(
            isElementOfType(
                shallowRenderer.getRenderOutput().props.children.type,
                Title
            ),
        true);
    });
});

Extending ShallowRendering with skin-deep

​skin-deep wraps shallowRender with some useful extras to access instance methods

import React from 'react';
import sd from 'skin-deep';

const tree = sd.shallowRender(React.createElement(MyComponent, {}));
const instance = tree.getMountedInstance();
const vdom = tree.getRenderOutput();

Check against expected children

import React from 'react';
import sd from 'skin-deep';

let HelloMessage = ({ name }) => 
    <div>
      <Title text={name}/>
      <Title2/>
    </div>;

let Title = ({ text }) => <h1 className="title">{text}</h1>
let Title2 = _ => <div>Dummy Test</div>;

let children = [
 <Title text="Vladimir"/>,
 <Title2/>
]

describe('Hello Message - check for children type', () => {
    it('Hello Message renders children of type Title, () => {
        const tree = sd.shallowRender(<HelloMessage name="Vladimir"/>);
        const vdom = tree.getRenderOutput();
        expect(vdom.props.children).to.deep.equal(children);
    });
});

Isolating and testing instance methods

Skin deep wraps gives us .getMountedInstance method which lets us access instance methods and test them in isolation

Overriding Inner Dependencies

import axios from 'axios';
import ChildComponent from 'childComponent';

export default class MyFancyWrapperComponent extends React.Component {

    componentWillMount(){
        axios.get('/user?ID=12345')
          .then(function (response) {
            console.log(response);
          })
          .catch(function (response) {
            console.log(response);
          });
    }

    render() {
        return (<div className="wrapper-style">
            <ChildComponent {...this.props} />
        </div>);
    }
}

Problem:

Inner module dependencies are private for a module and cannot be accessed via test

Solution

babel-plugin-rewire

A Babel plugin that adds the ability to rewire module dependencies.

{test: /src\/js\/.+\.js$/, loader: 'babel-loader?plugins=rewire' }

Webpack setup

rewire plugin is based on rewire.js but is itegrated into babel as transformer to support ES6 module syntax

Overriding dependencies

import ComponentToTest from 'my-fancy-wrapper-component-module';
function mockAjax () {
   //...
   return new Promise(...);
}

ComponentToTest.__Rewire__('axios', mockAjax );
//....Assertion is here
ComponentToTest.__ResetDependency__('axios');

Also supports top level variables and require('myModule') overriding in the same manner

__RewireAPI__

You can rewire module dependencies without importing a module

import { __RewireAPI__ as componentRewireAPI } from 'my-fancy-wrapper-component-module';
function mockAjax () {
   //...
   return new Promise(...);
}

componentRewireAPI.__Rewire__('axios', mockAjax );
//....Assertion is here
componentRewireAPI.__ResetDependency__('axios');

Flux Testing strategies

  • Try to avoid putting logic in store constructor
  • Export only store class and move store creation logic to separate module
  • Try to create store functions as thin and independent as possible (easier to test)
  • Override Dispatcher.register callback  to test store methods
  • Override Dispatcher.dispatch to independently test actions
  • Simulate events and test component shallow rendered props for E2E testing

Flux Testing strategies (part2)

Redux testing strategies

Almost everything is pure functions so the testing is even simpler. Less inner dependencies

Basic TDD workflow

  • Create Component Test
  • Test if component renders successfully
  • Write Component class

Components Creation part 1

Basic TDD workflow

  • Decide which dumb component it will have
  • Write test to check if it renders these components
  • Write empty dumb components to pass test

Components Creation part 2

Basic TDD workflow

  • test if initialState is passed to component
  • flux(Create Store for component)
  • redux(create reducer for component)
  • connect Store/Reducer to component to pass the test

Store/Reducer connection

Basic TDD workflow

  • test if ActionCreators return expected payload
  • write ActionCreators to pass test

Action Creators

Basic TDD workflow

  • test if Simulating events triggers dispatch
  • wire your component events to ActionCreators

Handling Events

Basic TDD workflow

  • test if Store/Reducer state is changed based on dispatch
  • write Store/Reducer methods to pass tests

Testing Store/Reducer

Keep

Calm

And

TDD

We are hiring!

react@90min.com

React Testing

By vladimirnovick

React Testing

  • 1,020
Loading comments...

More from vladimirnovick