React Testing
React Testing
Lets face it, testing is important but testing javascript UIs is not often straightforward.Now for React. React is a excellent choice for your front-end view logic. I love React for how productive it makes me feel. However, one downside of the React ecosystem is that there are so many choices to make in terms of what tools you will use. Varying choices include Webpack, Browserify, ES6 and Babel to manage your state, business logic and request data. It requires a lot of sifting through. To make it easy, here is a set of good choices for testing React that have worked well for us.
Tools
Mocha & Chia
Mocha is a test runner that has been around for a good while. It provides describe() and it() functions for BDD style testing. It is reminiscent of Rspec in the Ruby world. Chia is often used with Mocha as it brings a whole host of assertions for whichever style of testing you like.
Chia
With Chia you have the options of using expect, should, and assert style assertions making it flexible for anyone's taste.
Enzyme
Enzyme is a React component testing library from airbnb. It provides multiple, optimized ways to render React components and make assertions against them. Enzyme uses JSdom for some of its functionality and will be included as a dependency.
Optional Tools
Babel Register
If you are using ES6, this is a must as it compiles your tests and app code on the fly, enabling you to write your tests in ES6.
Sinon
If you need mocking I suggest looking at Sinon. With that said, I have not needed much mocking in my testing of React components and I suggest seeing how far you can go without having to add mocking.
Redux Testing (bonus)
There is an added bonus if you are using Redux, hence you will want to grab redux-mock-store. It enables you to set up a store with exactly the state that you want, making the testing of asynchronous actions easier.
Install Tools
Required tools:
npm install -g mocha
npm install --save-dev mocha chai enzyme jsdom jsdom-global react-addons-test-utils
Optional tools:
Babel Register and presets:
npm install --save-dev babel-core babel-preset-airbnb babel-preset-es2015 babel-preset-react babel-preset-stage-0
Sinon:
npm install --save-dev sinon
Redux Mock Store:
npm install --save-dev redux-mock-store
Setup
Here is what to do once you have installed everything.
Mocha.opts
Mocha.opts is a config file where you can define default options that will always be run when you use the Mocha command:
--compiler: defines a compiler to use before files are required
--require: used to require test suite dependancies
--recursive: runs all tests in ./test and tests in sub-directories in ./test
--ui: changes the functions that you use for Mocha
--compiler js:babel-register
--require ignore-styles
--require jsdom-global/register
--require ./test/test_helper.js
--recursive
--ui bdd
./test/mocha.opts
Test Helper
It is helpful to create a test-helper.js where you handle testing dependancies and any code that needs to run before the test suite fires up.
import { expect } from 'chai';
import sinon from 'sinon';
global.expect = expect;
global.sinon = sinon;
./test/test_helper.js
Package.json
Register your test command in Package.json
...,
"scripts": {
"test": "mocha",
"test:watch": "npm test -- --watch"
}
package.json
Let's Test
You should now have a working testing setup for React, but lets take our shiny, new setup for a test drive. This is going to be an involved example with some of the optional tools.
Component
import React, { Component } from 'react'
import Auth from './AuthService'
export default class LoginForm extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: ''
}
}
componentWillMount() {
if (Auth.user !== undefined) {
Auth.goToDashBoard()
}
}
onSubmitForm() {
Auth.Login(this.state)
}
render() {
return (
<div>
<div>
<label>Email</label>
<input
type="text"
value={this.state.email}
ref={(ref) => this.emailField = ref}
onChange={() => this.setState({email: this.emailField.value})}/>
</div>
<div>
<label>Password</label>
<input
type="password"
value={this.state.password}
ref={(ref) => this.passwordField = ref}
onChange={() => this.setState({password: this.passwordField.value})}/>
</div>
<button
type="button"
onClick={this.onSubmitForm}>
Login
</button>
</div>
)
}
}
Tests
We need to import react so jsx works in this file
import React from 'react'
import { shallow } from 'Enzyme'
import LoginForm from '../src/LoginForm'
describe('LoginForm', () => {
it('renders 2 inputs and a button', () => {
var wrapper = shallow(<LoginForm />)
expect(wrapper.find('input')).to.have.length(2)
expect(wrapper.find('button')).to.have.length(1)
})
it('test lifecycle method, redirects if user exists', () => {
var wrapper = shallow(<LoginForm />)
expect(wrapper.instance().componentWillMount.calledOnce).to.be
expect('check that redirect happened').to.be
})
it('test instance method', () => {
var wrapper = shallow(<LoginForm />)
var login = wrapper.instance().onSubmitForm()
expect('check that login happened').to.be
})