Day 1, Segment 5, Testing

In notebook:
FrontEndMasters React Intro 2
Created at:
2016-12-24
Updated:
2016-12-24
Tags:
JavaScript Webpack libraries React

Unit Testing

36:30

V1 of the workshop uses Mocha, sinon, chai, enzyme. This is still current and does everything you need.

Jest library for testing

Jest was created by Facebook. Jest encapsulates Jasmine.

Branch v2-11

On a previous project Jest was a catastrophe. It has been updated since and rewritten. They just kept the name and the logo. It still based on Jasmine (v2).

Snapshot test

Jest renders your component out to a file and compares it in the future as you work. Every time the component changes, you get a diff and can decide to update the test, or you had a regression. 
  // ****   Seach.spec.js

import React from 'react'
import Search from './Search'
// library that helps you do snapshot test
import renderer from 'react-test-renderer'

test('Search snapshot test', () => {
  const component = renderer.create(<Search />)
  const tree = component.toJSON()
  expect(tree).toMatchSnapshot()
})
Jest will find .spec.js files automatically. (also .test.js)

He puts the tests in the same directory as the source files, since they can serve as documentation.

react-test-renderer

library that helps you do snapshot test

First time it runs, it will create the snapshot. 

Update Babel to compile modules in a test scenario

The problem we have now is that we don't compile modules with Babel so the tests can't run, since they run with NodeJS (Jest is a NodeJS process). 
  // ****   .babelrc    ****
{
  "presets": [
    "react",
    ["es2015", {"modules": false, "loose": true}]
  ],
  // specify the test environment
  "env": {
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}
​then run the test
​$ NODE_ENV=test jest

you need to empty the jest cache first: $ NODE_ENV=test jest --no-cache

Jest caches the babel output

50:47

Now you have an __snapshots directory. the snapshots are not compressed, and readable.

Branch v2-12

Breaking the test

changes the text in ​<h1>​ and now it shows the diff.  ​$ NODE_ENV=test jest -u​ to update your snapshot. 
Brian really likes this, mostly because they are very cheap to write. 

It can do watch as well:
​$ NODE_ENV=test jest --watch
It (probably) test only what has changed. 

One problem: it reports the test failing in the parent component and not the one where it actually fails. 
To resolve this, we need to bring in Enzyme. 

(first, update the npm script to run test)
    // ****   package.json    ****
  
  ..
  "scripts": {
    "lint": "eslint js/**/*.js webpack.config.js",
    "build": "webpack",
    "dev": "webpack-dev-server",
    "watch": "npm run build -- --watch",
    // 1. ++++
    "test": "NODE_ENV=test jest",
    "update-test": "npm run test -- -u"
  },
Now you can just ​$ npm test​ (npm provides this shortcut to run tests)

Enzyme

Created by Airbnb. The test library Facebook uses. 
Update Search.spec.js
  // ****   Search.spec.js    ****

import React from 'react'
// 1. ++++
import { shallow } from 'enzyme'
// 2. ++++
import { shallowToJson } from 'enzyme-to-json'
// 4. ---- remove this ↓ (Enzymes already uses it internally)
// import renderer from 'react-test-renderer'
import Search from './Search'
import preload from '../public/data.json'


test('Search snapshot test', () => {
  // 3. ++-- update the test
  const component = shallow(<Search />)
  const tree = shallowToJson(component)
  expect(tree).toMatchSnapshot()
})

test('Search should render a ShowCard for each show', () => {
  const component = shallow(<Search />)
  expect(component.find(ShowCard).length).toEqual(preload.shows.length)
})

He's only using Shallow from Enzyme.
Shallow will not render the child components so it will resolve the problem described above. It will only go one level deep. Then shallow-to-json will convert it, so that we can do a snapshot. 

Jest just compares two JSON structures

All that Jest does is to compare two JSON structures. so you can use it many scenarios (compare server responses).

Now Enzyme just outputs the JSX version. Let's update the npm scripts and add the flags.
  // **** package.json

  "scripts": {
    "lint": "eslint js/**/*.js webpack.config.js",
    "build": "webpack",
    "dev": "webpack-dev-server",
    "watch": "npm run build -- --watch",
    "test": "NODE_ENV=test jest",
    "update-test": "npm run test -- -u"
  },
​$ npm run update-test​ 

For important, display components this type of test can be useful. 

Test ShowCard

Test that it inserts all shows (the number should equal, see below)
  // ****   Search.spec.js    ****

import React from 'react'
import { shallow } from 'enzyme'
import { shallowToJson } from 'enzyme-to-json'
import Search from './Search'
// 1. ++++ import showcard and preload data
import ShowCard from './ShowCard'
import preload from '../public/data.json'

test('Search snapshot test', () => {
  ...
})

// 2. ++++
// want to test, that it renders 15 showcards for 15 shows
test('Search should render a ShowCard for each show', () => {
  const component = shallow(<Search />)
  // find can receive a React component
  expect(component.find(ShowCard).length).toEqual(preload.shows.length)
})

​Component.find()

It can receive a React component...

Now, if the search term uses a predefined word, it breaks, since we filtered the list.

Assertions - Test the search

  test('Search should render correct amount of shows based on search', () => {
  const searchWord = 'house'
  const component = shallow(<Search />)
  component.find('input').simulate('change', {target: {value: searchWord}})
  // just copied this from the component ↓
  const showCount = preload.shows.filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(searchWord.toUpperCase()) >= 0).length
  // test the count
  // you could also test for content here (but he prefers simpler tests)
  expect(component.find(ShowCard).length).toEqual(showCount)
})

​.simulate

Can simulate, (fire) an event. Enzyme does not to event bubbling, so you have to do it where the handler is. 

The best feature about Enzyme is the shallow test. Doesn't use PhantomJS, bootstrapping or anything, so it's pretty fast (32ms for this test, probably won't grow too higher as you add more).

For integration tests he "recommends" Selenium. 

Avoid pulling in renderer, because it will slow down the tests. If you want to interact with DOM API, then you will need to bring in the renderer. 
Cheerio for static analysis of DOM. Try to avoid bringing in Phantom.

You can use Enzyme with Mocha. 

Testing strategy

He doesn't test the React components much. He abstracts the business logic into modules and tests them thoroughly. 

Branch v2-13

Test coverage

Already built into Jest. Gives the test coverage report with Istanbul. The result a bit "interesting". He gets almost 100% coverage, even though he hardly wrote any test.

You can open a the report in your browser (Istanbul).

Add shortcuts to npm scripts 

    // **** package.json  ****
  
  "scripts": {
    "lint": "eslint js/**/*.js webpack.config.js",
    "build": "webpack",
    "dev": "webpack-dev-server",
    "watch": "npm run build -- --watch",
    "test": "NODE_ENV=test jest",
    "update-test": "npm run test -- -u",
    // 1. ++++
    "cover": "npm run test -- --coverage"
  },
​$npm run cover​ to get the coverage

Hot module reload

He will not use this in this workshop. The versions are in-between so we have to wait. He will update the workshop notes, once the stable version comes out.