Testing with Jest

Joe Buza

What is Jest?

Jest is a JavaScript testing framework, used by Facebook to test all JavaScript code including React applications.

Why Jest?

  • Zero configuration

  • Fast

  • Snapshot testing

Zero Configuration

Jest virtualizes the javascript environment using jsdom and manages all the configurations inside the library.

Small Config File

// package.json
  "dependencies": {
    "react": "<current-version>",
    "react-dom": "<current-version>"
  },
  "devDependencies": {
    "babel-jest": "<current-version>",
    "babel-preset-es2015": "<current-version>",
    "babel-preset-react": "<current-version>",
    "jest": "<current-version>",
    "react-test-renderer": "<current-version>"
  },
  "scripts": {
    "test": "jest"
  }


// .babelrc
{
  "presets": ["es2015", "react"]
}
// package.json
  "devDependencies": {
    "jest": "<current-version>"
  },
  "scripts": {
    "test": "jest"
  }

js

react

Fast

Jest runs tests in paraellel across worker processes

Jest allows you to run tests that have changed. Uses git to detect the last file changed since the last commit.

jest -o

Jest Features

  • watch mode
  • code coverage
  • helpful error messages

jest --watch

lets you automatically run the test suite on every file change.

"scripts": {
    "watch:test": "jest --watch"
 }

jest --coverage

{
"scripts": {
    "test": "jest --coverage"
  },
"jest": {
    "testEnvironment": "node",
    "collectCoverageFrom": [
      "src/*.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 100,
        "functions": 50,
        "lines": 33,
        "statements": 25
      }
    }
  }
}

Helpful Error Messages

  • color coded

  • stack traces 

Getting Started

My Configuration

{
  "name": "testing-with-jest",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "enzyme": "^2.7.0",
    "enzyme-to-json": "^1.4.5",
    "react-addons-test-utils": "^15.4.2",
    "react-dom": "^15.4.2",
    "react-scripts": "0.8.5"
  },
  "dependencies": {
    "react": "^15.4.2",
    "react-dom": "^15.4.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "jest": {
    "testEnvironment": "node",
    "collectCoverageFrom": [
      "src/*.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 100,
        "functions": 50,
        "lines": 33,
        "statements": 25
      }
    }
  }
}

Snapshot Testing

Jest can capture snapshots of React trees or other serializable values to simplify UI testing.

Before Snapshot Testing

Developers used Jest + Enzyme

Enzyme

JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output

Link Component

import React, {Component, PropTypes} from 'react'

const STATUS = {
    NORMAL:'normal',
    HOVERED:'hovered'
}

class Link extends Component{
    static propTypes = {
        page: PropTypes.string,
        style: PropTypes.object 
    }
    state = {
        class: STATUS.NORMAL
    }
    _handleMouseEnter = () => {
        this.setState({class: STATUS.HOVERED})
    }
    _handleMouseLeave = () => {
        this.setState({class: STATUS.NORMAL})
    }
  render(){
      return (
        <a 
            style={{...style, ...this.props.style}}
            className={this.state.class}
            href={this.props.page || '#'}
            onMouseEnter={this._handleMouseEnter}            
            onMouseLeave={this._handleMouseLeave}            
        >
        {this.props.children}
        </a>
      )
  } 
}

const style = {
    textShadow: '2px 2px darkblue'    
}
export default Link
import React from 'react'

// Third Party
import { shallow } from 'enzyme'

// Local 
import Link from '../Link'

describe('<Link/>', ()=>{
   let wrapper
   beforeEach(()=>{
       wrapper = shallow(
           <Link page=""/>
       )
   }) 
   it('should render', ()=>{
       expect(wrapper).toBeDefined()
   }) 
   it('should have class normal by default', ()=>{
       expect(wrapper.props().className).toBe('normal')
   })
   it('should have class hovered when mouse enters', ()=>{
       wrapper.simulate('mouseEnter')
       expect(wrapper.props().className).toBe('hovered')
   })
   it('should have class normal when mouse leaves', ()=>{
       wrapper.props().onMouseEnter()
       wrapper.props().onMouseLeave()
       expect(wrapper.props().className).toBe('normal')
   })
   it('should have href of # when no page is passed in', ()=>{
       expect(wrapper.props().href).toBe('#')
   })
   it('should have correct link when passed in', ()=>{
       const page = "https://www.facebook.com"
       wrapper.setProps({page})
       expect(wrapper.props().href).toBe(page)
   })
   it('should pass children', ()=>{
       const children = 'MY LINK'
      wrapper.setProps({children}) 
        expect(wrapper.text()).toMatch(children)
   })
})

Test using Enzyme

Result

Snapshot Test

// Link.react-test.js
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('Link changes the class when hovered', () => {
  const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseLeave();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

Snapshot

exports[`<Link/> snapshots Link changes the class when hovered 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
  style={
    Object {
      "textShadow": "2px 2px darkblue",
    }
  }>
  Facebook
</a>
`;

exports[`<Link/> snapshots Link changes the class when hovered 2`] = `
<a
  className="hovered"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
  style={
    Object {
      "textShadow": "2px 2px darkblue",
    }
  }>
  Facebook
</a>
`;

exports[`<Link/> snapshots Link changes the class when hovered 3`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
  style={
    Object {
      "textShadow": "2px 2px darkblue",
    }
  }>
  Facebook
</a>
`;

Pros

  • Writing tests is easy

  • Easier inspection

  • Saves time

  • Provides a clear view of what changed after each update whether major or minor​

Cons

  • Requires manual verification

  • Easy to accept snapshots that have problems.

  • Can create fragile tests.

Use with care

A robust code review process is required.

Future is bright

upcoming feature

Resources

Questions?