Testing React Apps

Nordnet Academy

  • We all do it, right?

TDD

No TDD or tests?

Testing

  • Improve quality
  • Faster feedback
  • Keep old code working
  • Documentation

Testing React

What to test?

  • Testing structure

 

Given properties and state what would be the structure of a rendered tree?

  • Testing behaviour

 

Given an output of render(), is it possible to transition from state A to state B?

Testing Structure

Real DOM

  • Render using real DOM
  • TestUtils.renderIntoDocument()
    • 'react/addons' (0.13)
    • 'react-addons-test-utils' (0.14)
  • React.findDOMNode() (0.13)
  • ReactDOM.findDOMNode() (0.14)

Real DOM

import React from 'react/addons';
// import React from 'react'; // only React 0.14.x
// import ReactDOM from 'react-dom';
// import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';
import Component from 'component';

const TestUtils = React.addons.TestUtils; // only React 0.13.x

describe('<Component />', () => {
    let node;

    beforeEach(() => {
        const component = TestUtils.renderIntoDocument(<Component />);
        // node = ReactDOM.findDOMNode(component); // only React 0.14.x
        node = React.findDOMNode(component);
    });

    it('should render div', () => expect(node.tagName).to.equal('DIV'));
});

Real DOM

Pros

Cons

  • Depends on environment (PhantomJS, jsdom)
  • Harder to test React specific things like props/state
  • Can't test component tree, only DOM tree
  • Querying DOM directly could be verbose

TestUtils

  • Testing using virtual DOM
  • Methods to query DOM
  • Methods to simulate events
  • Possible to test component tree, not only DOM tree
  • Possible to verify React specific things like pros, refs, keys

TestUtils

import React from 'react';
import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';
import Component from 'component';
import ComponentList from 'component-list';

describe('<ComponentList />', () => {
    let component;
    const values = { 0: 'value_0', 1: 'value_1'};

    beforeEach(() => component = TestUtils.renderIntoDocument(<ComponentList />));

    it('shows a list of components', () => {
        const list = TestUtils.findRenderedDOMComponentWithTag(component, 'ul');
        expect(list.props.children.length).toEqual(2);
    
        list.props.children.forEach((item, index) => {
          expect(item.type).toEqual(Component);
          expect(item.key).toEqual(index);
          expect(item.ref).toEqual(`component_${index}`);
          expect(item.props.value).toEqual(values[index]);
        });
    });
});

TestUtils

Pros

  • Closer to render() output structure
  • Possible to test components, not just DOM nodes
  • Possible to test React specific things like props, state, keys, refs
  • Possible to test behaviour by triggering events, setting state or calling properties

Cons

  • Depends on environment (PhantomJS, jsdom)
  • Could be as verbose as querying DOM directly
  • Not possible to test components in isolation

Shallow Rendering

  • Testing components in isolation
    • only renders top level component
    • else is kept as React elements
  • Possible to access state and props
  • Under development, lack of tools

Shallow Rendering

import React from 'react';
import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';
import Component from 'component';
import ComponentList from 'component-list';

describe('<ComponentList />', () => {
    let output;

    beforeEach(() => {
        const renderer = TestUtils.createRenderer();
        renderer.render(<ComponentList />);
        output = renderer.getRenderOutput();
    });

    it('shows a list of components', () => {
        expect(output.props.children).toEqual([
            <li><Component key="0" refs="component_0"/></li>,
            <li><Component key="1" refs="component_1"/></li>
        ]);
    });
});

Shallow Rendering

Pros

  • Real DOM is not required
  • Complete isolation when testing components
  • Possible to test React specific features

Cons

  • No access to lower level components
  • Currently refs don't work
  • Still under development and lacks features
  • Lack of tools to traverse React components tree
  • Call getRenderOutput() manually when component is rerendered

Testing using refs

  • Combines real DOM and TestUtils approaches
    • Shares pros and cons of both as well
  • Less DOM querying by using refs to access nodes
  • Adding code in components for the sake of testing

Testing using refs

import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';
import ComponentList from 'component-list';

describe('<Component />', () => {
    let component;

    beforeEach(() => component = TestUtils.renderIntoDocument(<ComponentList />));

    it('should render div', () => {
        const listDOM = ReactDOM.findDOMNode(component.refs.list);
        expect(listDOM.tagName).to.equal('UL')
    });
});

Testing Behaviour

TestUtils.Simulate

  • Simulate events - click, hover, change input values, key press, etc.
  • React has own synthetic events system
    • don't trigger events directly

TestUtils.Simulate

// <input ref="input" />
var node = this.refs.input;
node.value = 'giraffe'
TestUtils.Simulate.change(node);
TestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13});

TestUtils.Simulate

Pros

  • Simulate user input and interactions
  • Suits both Real DOM and TestUtils approaches
  • Proper way to test that event handlers are setup correctly

Cons

  • Currently requires real DOM
  • Event listeners should be setup before testing behaviour
  • Only testing user interactions

Test using setState()

  • Calling setState() directly on component
  • No requirements on the DOM

 

  • Allows setting arbitrary state, even impossible one
  • Might end up testing cases which cannot be reached by your application code
  • Should be considered an anti-pattern, try to avoid

Calling properties and methods

  • Test state changes that are not caused by user interactions
    • can't test with TestUtils.Simulate
  • Call component methods directly
  • Works with both real DOM and shallow rendering approaches

Enzyme

Enzyme

  • Shallow rendering
    • unit testing
    • isolation
  • JSDOM full rendering
    • interactions with DOM
    • lifecycle methods
  • Static rendered markup
    • static HTML markup
    • Cheerio for HTML parsing

Enzyme

import React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import Component from 'component';
import ComponentList from 'component-list';

describe('<ComponentList />', () => {
    let component;

    beforeEach(() => component = shallow(<ComponentList />));

    it('shows a list of components', 
        () => expect(component.find(Component)).to.have.length(2));
});

Enzyme

import React from 'react';
import { expect } from 'chai';
import Component from 'component';

import {
  describeWithDOM,
  mount,
  spyLifecycle,
} from 'enzyme';

describeWithDOM('<Foo />', () => {
    let component;

    beforeEach(() => {
        spyLifecycle(Foo);
        component = mount(<Component />);
    });

  it('calls componentDidMount', 
    () => expect(Component.prototype.componentDidMount.calledOnce).to.equal(true));

});

Questions?

Made with Slides.com