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
- Less dependent on frameworks tools
- Can use Document Object Model Web API directly
- Most direct way to test outcome
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
- Javascript Testing utility for React
- Flexible API for DOM traversal and manipulation
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?
Testing React Applications
By nordnetacademy
Testing React Applications
Introduction to testing React applications
- 1,504