the many ways to test your react app

96
The Many Ways to Test React All Things Open 2016 #ATO2016 Van J. Wilson @vanwilson

Upload: all-things-open

Post on 16-Feb-2017

178 views

Category:

Technology


5 download

TRANSCRIPT

The Many Ways to Test React

All Things Open 2016#ATO2016

Van J. Wilson@vanwilson

Who I Am

Who I Am

Who You Are?

Who You Are

• Know Javascript

• Already familiar with React

• Know a little bit about testing (vanilla Javascript, another Javascript library/framework, or even in another language)

• Want to write better software

How most devs actually test

(Look closely; she’s hitting CTRL-R over and over again.)

Why Write Automated Tests?

https://twitter.com/housecor/status/714430381680775168

Why Write Automated Tests?

https://twitter.com/jjafuller/status/714595373059166208

Benefits of TestingTesting does …

➡ … help you focus on what’s important in the code➡ … give you confidence that—if you change

something about the code—it still does the same stuff it did before

Testing doesn’t …➡ … prove that your code is “correct”➡ … guarantee the absence of bugs.

Benefits of Testing

• Defensive driving doesn’t guarantee that you won’t get in an accident.

• Wearing a seat belt doesn’t guarantee that you won’t get hurt if you do have an accident.

That doesn’t mean you should not wear a seatbelt or drive defensively.

“It is a profoundly erroneous truism, repeated by all copy-books and by eminent people when

they are making speeches, that we should cultivate the habit of thinking of what we are

doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform

without thinking about them. Operations of thought are like cavalry charges in a battle — they are strictly limited in number, they require

fresh horses, and must only be made at decisive moments.”

–Alfred North Whitehead, An Introduction to Mathematics

Test-Driven Development

FAIL

PASSREFACTOR

1. Write Tests

2. Run Tests (TESTS FAIL)

3. Write Code to Make Tests Pass

4. Run Tests (TESTS PASS)

5. Refactor (repeat until you can’t think of anything else to test)

Test-During Development

CODE

FAILPASS

1. Write Some Code

2. Write Some Tests

3. Run Tests (TESTS FAIL)

4. Write More Code

5. Run Tests (TESTS PASS)

6. Refactor (repeat until all the features and tests are done)

Kinds of automated testing

• Unit Testing

• Integration Testing

• Functional / Acceptance Testing

Kinds of automated testing

Unit

Integration

Functional

xUnit

Test Harness

Selenium

Kinds of automated testing

• Unit Testing

• Integration Testing

• Functional / Acceptance Testing

Why testing React is easy

React apps are meant to be predictable,

and consequently they are eminently testable.

React components == mathematical functions

y = mx +b

where m is the slope of the line, x is the x-coordinate, b is the y-intercept

React Components

• Hierarchical

• Encourage one-way data flow

• Ultimately, just Javascript functions

Even JSX is just functions

JSX is functionsconst element = ( <h1 className="greeting"> Hello, world! </h1>);

const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!');

Library Fatigue

Required files"devDependencies": { "babel": "^6.5.2", "babel-core": "6.11.4", "babel-loader": "^6.2.4", "babel-polyfill": "^6.13.0", "babel-preset-es2015": "6.9.0", "babel-preset-react": "6.11.1", "babel-register": "^6.14.0", "chai": "^3.5.0", "chai-enzyme": "^0.5.1", "chalk": "1.1.3", "enzyme": "^2.4.1", "eslint": "3.1.1", "eslint-loader": "1.4.1", "expect": "^1.20.2", "istanbul": "^0.4.5", "istanbul-instrumenter-loader": "^0.2.0", "karma": "^1.2.0", "karma-chai": "^0.1.0", "karma-chai-plugins": "^0.7.0", "karma-chrome-launcher": "^2.0.0",

"karma-cli": "^1.0.1", "karma-coverage": "^1.1.1", "karma-firefox-launcher": "^1.0.0", "karma-junit-reporter": "^1.1.0", "karma-mocha": "^1.1.1", "karma-mocha-reporter": "^2.1.0", "karma-phantomjs-launcher": "^1.0.1", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "0.0.26", "karma-webpack": "^1.8.0", "mocha": "^3.0.2", "nock": "^8.0.0", "phantomjs-polyfill": "0.0.2", "phantomjs-prebuilt": "^2.1.12", "react-addons-test-utils": "^15.3.1", "redux-mock-store": "^1.1.4", "sass-loader": "^3.1.2", "sinon": "^1.17.5", "webpack": "1.13.1"}

Just kidding!

The real minimum requirements

• Test Framework

• A DOM

• A test utility library that “speaks React”

An aside: It’s still React, though

So, we’re going to need a few basic building blocks in order to:

• Compile JSX

• Transpile ES6 (if you want to use it)

• Bundle the libraries with your code

• Include React and ReactDOM (duh)

Bare-bones React setup "dependencies": { "react": "^15.3.1", "react-dom": "^15.3.1" }, "devDependencies": { "babel-core": "^6.14.0", "babel-loader": "^6.2.5", "babel-polyfill": "^6.13.0", "babel-preset-es2015": "^6.14.0", "babel-preset-react": "^6.11.1", "live-server": "^1.1.0", "npm-run-all": "^3.1.0", "webpack": "^1.13.2" }

Get it all at: https://gist.github.com/vjwilson/a040c234c0d6683c1341331f57a81e77

Test Frameworks

Test Frameworks

• Jasmine - mature, built-in assertions and spies

• Jest - wrapper around Jasmine for React

• Tape - straightforward, traditional paradigm

• Ava - built-in ES2015, concurrent test running

• Mocha - modular, allows (requires) separate assertion and spy libraries

How to decide? Just ask?

https://twitter.com/housecor/status/717759300886618112

Test Frameworks• Jasmine - mature, built-in assertions and spies

• Jest

• Tape - straightforward, traditional paradigm

• Ava - built-in ES2015, concurrent test running

• Mocha

npm install —save-dev mocha #(or) jest

A little bait-and-switch

Mocha requires you to choose a separate assertion library

Assertion Libraries• Assert - built into Node, just require/import

• Expect - basic BDD style, similar to Jasmine

• Should - uber BDD style, bleeding edge

• Chai - can mimic any of the three libraries

• Power-Assert - concentrates on “truthy” values

• UnitJS - similar to Chai

Assertion Libraries• Assert - built into Node, just require/import

• Expect - basic BDD style, similar to Jasmine

• Should - uber BDD style, bleeding edge

• Chai

• Power-Assert - concentrates on “truthy” values

• UnitJS - similar to Chai

npm install —save-dev chai

Options for a test DOM

• A real browser (Chrome, Firefox, Safari etc)

• PhantomJS (a “headless” browser)

• JSDom

Real browsers or PhantomJS will require some “glue” in the form of something like Karma.

Options for a test DOM• A real browser (Chrome, Firefox, Safari etc)

• PhantomJS (a “headless” browser)

• JSDom

npm install —save-dev jsdom

Helper Libraries

• React Test Utils - from Facebook, bare-bones

• React Shallow Testutils - some helper wrappers

• Enzyme - like jQuery, but for tests

• Sinon - spies, mocks, and stubs

Helper Libraries• React Test Utils

• React Shallow Testutils

• Enzyme

• Sinon

npm install —save-dev react-test-utils react-shallow-testutils enzyme simon

One more thing… spiesMocha doesn’t come with a mocking library, so to test the behavior of functions thoroughly, you’ll need a mocking library.

One more thing… spiesSinon is the most popular mocking library for Mocha.

npm install —save-dev sinon

Where Do Tests Go?

In their own folder With the code they are testing

Enough setup…Let’s run some tests!

The Example App

https://github.com/vjwilson/many-ways-to-test-react

The Example App

Ex1: ES5

• React.createClass (ES5) style components

• Webpack/Babel compiles JSX to a static bundle

• Live Server serves and auto-refreshes the page

• No tests yet (Test-During Development)

Ex1: The App component

var App = React.createClass({ getInitialState: function() { return { castMembers: this.props.initialCastMembers, user: this.props.initialUser }; },

loginAction: function() { this.setState({ user: this.props.initialUser });; },

logoutAction: function() { this.setState({ user: null });; },

ex1-react-es5/src/App.js (excerpt)

Ex1: App (cont.)

render: function() { return ( <div className="container"> <Header user={this.state.user} login={this.loginAction} logout={this.logoutAction} /> <Jumbotron /> <div className="panel panel-default"> <div className="panel-heading"> <h2 className="panel-title">Top-Billed Cast</h2> </div> <div className="panel-body"> <CastMemberList members = {this.state.castMembers} /> </div> </div> </div> ); }});

Ex2: Mocha and React Test Utils

• Mocha test framework

• JSDom

• React Test Utils (and a little Shallow Tests Utils)

• Chai’s expect function

• Sinon for function spies

Ex2: What changed in the tooling

"scripts": { // build stuff … "test": "mocha --reporter spec test/helpers/testSetup.js \"test/**/*.test.js\"", "test:watch": "npm run test -- --watch" }, "devDependencies": { // other dependencies … "chai": "^3.5.0", "jsdom": "^9.5.0", "mocha": "^3.0.2", "react-addons-test-utils": "15.0.2", "react-shallow-testutils": "^2.0.0", "sinon": "^1.17.6", }

ex2-mocha-react-test-utils/package.json (excerpt)

Ex2: Test Setuprequire(‘babel-register')();

var jsdom = require('jsdom').jsdom;

var exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');global.window = document.defaultView;Object.keys(document.defaultView).forEach((property) => { if (typeof global[property] === 'undefined') { exposedProperties.push(property); global[property] = document.defaultView[property]; }});

global.navigator = { userAgent: 'node.js'};

documentRef = document;

ex2-mocha-react-test-utils/test/helpers/testSetup.js (excerpt)

Ex2: Test Setup (cont.)

{ "presets": [ "react", "es2015" ]}

ex2-mocha-react-test-utils/.babelrc (excerpt)

Ex2: Components and Tests!"" src#   !"" App.js#   !"" CastMember.js#   !"" CastMemberList.js#   !"" Header.js#   !"" Jumbotron.js#   $"" index.js!"" test#   !"" helpers#   #   $"" testSetup.js#   !"" App.test.js#   !"" CastMember.test.js#   !"" CastMemberList.test.js#   !"" Header.test.js#   $"" Jumbotron.test.js

React Test Utils• Replaces ReactDOM

• Can renderIntoDocument() or “shallow render”

• Functions to find rendered nodes (e.g.):• scryRenderedDOMComponentsWithClass()

• findRenderedDOMComponentWithClass()

• Simulate() to fire DOM events

• Function to test nodes (e.g.):• isElementOfType()

• isCompositeComponentWithType()

Ex2: What a Test Looks Like

var chai = require('chai');var React= require('react');var TestUtils = require('react-addons-test-utils');var CastMember = require('../src/CastMember');

const expect = chai.expect;

function setup(props) {

let renderer = TestUtils.createRenderer(); renderer.render(<CastMember {...props}/>); let output = renderer.getRenderOutput();

return { props, output, renderer };}

ex2-mocha-react-test-utils/test/CastMember.test.js (part 1 of 3)

Ex2: What a Test Looks Likedescribe('CastMember component', () => { let props = { member: { id: 3, name: 'Testy McTestface', imageUrl: 'test-image.jpg', thumbnailUrl: 'test-image-small.jpg', bio: 'This is a test.' } };

it('should render a div with correct class', () => { const {output} = setup(props);

expect(output.type).to.equal('div'); expect(output.props.className).to.equal('media'); });

ex2-mocha-react-test-utils/test/CastMember.test.js (part 2 of 3)

Ex2: What a Test Looks Like

it('should contain two child divs with appropriate classes', () => { const {output} = setup(props); const firstChild = output.props.children[0]; const secondChild = output.props.children[1];

expect(firstChild.type).to.equal('div'); expect(firstChild.props.className).to.equal('media-left');

expect(secondChild.type).to.equal('div'); expect(secondChild.props.className).to.equal('media-body'); });

it('should contain a heading element', () => { const output = TestUtils.renderIntoDocument(<CastMember {...props} />); const titleElement = TestUtils.findRenderedDOMComponentWithClass(output, 'media-heading');

expect(TestUtils.isDOMComponent(titleElement)).to.be.true; });});

ex2-mocha-react-test-utils/test/CastMember.test.js (part 3 of 3)

99 Problems with React Test Utils

• Limited API

• Verbose function names

• Too subtle and obtuse• What is scrying, anyway?

• Brittle and hard to pinpoint elements• var loginBlock =

output.props.children.props.children[1] .props.children[2].props.children; // Wut?

Shallow Test Utils// other imports…var ShallowTestUtils = require('react-shallow-testutils');var Header= require(‘../src/Header');

// other tests… it('should render a nav with username and logout link if user logged in', () => { props.user = user; var output = setup(props);

var navbarText = ShallowTestUtils.findWithClass(output, 'navbar-text'); var navbarLink = ShallowTestUtils.findWithClass(output, 'navbar-link'); expect(navbarText.props.children).to.equal('Welcome, ' + user.username); expect(navbarLink.props.children).to.equal('Logout'); });

ex2-mocha-react-test-utils/test/Header.test.js (excerpt)

Enzyme• Extends React Test Utils

• Can render a React DOM, shallow-render in element, or render the final HTML DOM

• Has a ton of jQuery-like selectors and helpers• <renderedOutput>.find(selector)

• <renderedOutput>.findWhere(fn)

• <renderedOutput>.text()

• <renderedOutput>..prop(key)

• and many, many more…

• https://github.com/airbnb/enzyme

Ex3: Mocha and Enzyme

"scripts": { // build stuff … "test": "mocha --reporter spec test/helpers/testSetup.js \"src/**/*.test.js\"", "test:watch": "npm run test -- --watch" }, "devDependencies": { // other dependencies … "chai": “^3.5.0", "enzyme": "^2.4.1", "jsdom": "^9.5.0", "mocha": "^3.0.2", "react-addons-test-utils": "15.0.2", "react-shallow-testutils": "^2.0.0", "sinon": "^1.17.6", }

ex3-mocha-react-test-utils/package.json (excerpt)

Ex3: Components with Tests!"" src#   !"" App#   #   !"" App.js#   #   $"" App.test.js#   !"" Cast#   #   !"" tests#   #   !"" CastMember.js#   #   $"" CastMemberList.js#   !"" Header#   #   !"" Header.js#   #   $"" Header.test.js#   !"" Jumbotron#   #   !"" Jumbotron.js#   #   $"" Jumbotron.test.js#   $"" index.js!"" test#   $"" helpers#   $"" testSetup.js

Ex3: What a Test Looks Like

import React from 'react';import { expect } from 'chai';import { shallow, mount, render } from 'enzyme';import sinon from 'sinon';import Header from './Header';

describe('Header component', function() { let props;

beforeEach(function() { props = { user: null, login: function() {}, logout: function() {} }; });

ex3-mocha-enzyme/src/Header/Header.test.js (excerpts, 1 of 4)

Ex3: What a Test Looks Like

it('should render the static component', function() { // sanity-check test // does it "render without exploding"? // see: https://gist.github.com/thevangelist/ e2002bc6b9834def92d46e4d92f15874

const shallowOutput = shallow(<Header user={props.user} login={props.loginAction} logout={props.logoutAction} />);

expect(shallowOutput).to.have.length(1); });

// more tests

ex3-mocha-enzyme/src/Header/Header.test.js (excerpts, 2 of 4)

Ex3: What a Test Looks Like

describe('calling the appropriate action props', function() { let loginAction; let logoutAction;

beforeEach(function() { loginAction = sinon.spy(); logoutAction = sinon.spy();

props.loginAction = loginAction; props.logoutAction = logoutAction; });

ex3-mocha-enzyme/src/Header/Header.test.js (excerpts, 3 of 4)

Ex3: What a Test Looks Like it('should call login function if no authenticated use', function() { const shallowOutput = shallow(<Header user={props.user} login={props.loginAction} logout={props.logoutAction} />); const authLink = shallowOutput.find('.login-block .navbar-link'); authLink.simulate('click'); expect(loginAction.calledOnce).to.be.true; });

it('should call logout function when there is an authenticated user', function() { props.user = { id: 3, username: 'gmtester' }; const shallowOutput = shallow(<Header user={props.user} login={props.loginAction} logout={props.logoutAction} />); const authLink = shallowOutput.find('.login-block .navbar-link'); authLink.simulate('click') expect(logoutAction.calledOnce).to.be.true; }); });

ex3-mocha-enzyme/src/Header/Header.test.js (excerpts, 3 of 4)

Ex4: Mocha and Enzyme, but in ES6

module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['react'] } }, ] }

ex1-react-es5/webpack.config.js (excerpt)

module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['es2015', 'react'] } }, ] }

ex2-mocha-react-test-utils/webpack.config.js (excerpt)

A digression — Jest

Remember this poll?

https://twitter.com/housecor/status/717759300886618112

9% usage?!?

Before / After Jest-pocalype

• Auto-mocking on by default

• Optimized inside Facebook, but not by default in open source setup

• Slow

< v15.0 v15.0+

• Auto-mocking off by default

• New commitment to community

• Snapshot testing (v14)

• Fast

Ex5: Mocha and Jest

• Jest test framework

• JSDom (included with Jest)

• expect function (included with Jasmine2, which is included with Jest)

• Spies (that’s right, included with Jasmine2, which is included with Jest)

Ex5: Tests in separate folder!"" __tests__#   !"" __snapshots__#   #   !"" App.test.js.snap#   #   !"" CastMember.test.js.snap#   #   !"" CastMemberList.test.js.snap#   #   !"" Header.test.js.snap#   #   $"" Jumbotron.test.js.snap#   !"" App.test.js#   !"" CastMember.test.js#   !"" CastMemberList.test.js#   !"" Header.test.js#   $"" Jumbotron.test.js!"" src#   !"" App#   #   $"" App.js#   !"" Cast#   #   !"" CastMember.js#   #   $"" CastMemberList.js#   !"" Header#   #   $"" Header.js#   !"" Jumbotron#   #   $"" Jumbotron.js#   $"" index.js

Ex5: What a Test Looks Like

import React from 'react';import renderer from 'react-test-renderer';import CastMember from '../src/Cast/CastMember';

test('CastMember rendered static HTML', () => { const props = { member: { id: 3, name: 'Testy McTestFace', imageUrl: 'test-image.jpg', thumbnailUrl: 'test-image-small.jpg', bio: 'This is a test.' } };

ex5-jest/__tests__/CastMember.test.js (part 1 of 2)

Ex5: What a Test Looks Like

const component = renderer.create( <CastMember {...props} /> ); let tree = component.toJSON(); expect(tree).toMatchSnapshot();});

ex5-jest/__tests__/CastMember.test.js (part 2 of 2)

One big advantage for Jest

Jest comes set up when you create a React app with

create-react-app,

Facebook’s React generator

Ex6: Mocha Enzyme, ES6, and React-Router

• Mocha test framework

• JSDom

• Enzyme

• Chai’s expect function

• Sinon for function spies

• React-Router

Ex6: A Second Page!

Ex6: React-Router folder structure!"" src#   !"" App#   !"" Cast#   !"" Header#   !"" Home#   !"" Jumbotron#   !"" Tickets#   !"" castMemberData.js#   !"" index.js#   !"" initialUserData.js#   !"" routes.js#   !"" routes.test.js#   $"" seatData.js!"" test#   !"" fixtures#   $"" helpers

Ex6: What a Test Looks Like

import React from 'react';import { expect } from 'chai';import { shallow, mount } from 'enzyme';import sinon from 'sinon';import BuyTicketsPage from './BuyTicketsPage';import SeatingChart from './Seating/SeatingChart';import TicketForm from './TicketForm';import mockSeatData from '../../test/fixtures/mockSeatData';

describe('BuyTicketsPage component', function() { let routeObject;

beforeEach(function() { routeObject = { initialSeatData: [] }; });

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 1 of 6)

Ex6: What a Test Looks Like

describe('rendering', function() { it('should render the component (smoke test)', function() {

const shallowOutput = shallow(<BuyTicketsPage route={routeObject} />);

expect(shallowOutput).to.have.length(1); });

it('should render an element with correct classes', function() { const shallowOutput = shallow(<BuyTicketsPage route={routeObject} />);

expect(shallowOutput.hasClass('text-center')).to.be.true; }); }); //…

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 2 of 6)

Ex6: What a Test Looks Like

describe('BuyTicketsPage component actions', function() { it('should handle a form submission from its TicketForm component (manual test)', function() { routeObject.initialSeatData = JSON.parse(JSON.stringify(mockSeatData));

const buyTicketsPage = mount(<BuyTicketsPage route={routeObject} />);

// manually set the state of selected seats and the package to buy let seatsToSelect = routeObject.initialSeatData[2].slice(0, 2); seatsToSelect[0].selected = true; seatsToSelect[1].selected = true;

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 3 of 6)

Ex6: What a Test Looks Like buyTicketsPage.setState({ ticketPackage: { seats: seatsToSelect } });

const ticketForm = buyTicketsPage.find('.ticket-form');

ticketForm.simulate('submit');

const seatArray = buyTicketsPage.state('seatData'); const ticketPackage = buyTicketsPage.state('ticketPackage');

expect(seatArray[2][0].sold).to.be.true; expect(seatArray[2][1].sold).to.be.true; expect(ticketPackage.seats).to.have.lengthOf(0); });

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 4 of 6)

Ex6: What a Test Looks Like

it('should handle a form submission from its TicketForm component (automatic test)', function() { routeObject.initialSeatData = JSON.parse(JSON.stringify(mockSeatData));

const buyTicketsPage = mount(<BuyTicketsPage route={routeObject} />);

// automatically set the state of selected seats and the package to buy const pageInstance = buyTicketsPage.instance();

pageInstance.updateSeatStatus('C1'); pageInstance.updateSeatStatus('C2');

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 5 of 6)

Ex6: What a Test Looks Like

const ticketForm = buyTicketsPage.find('.ticket-form');

ticketForm.simulate('submit');

const seatArray = buyTicketsPage.state('seatData'); const ticketPackage = buyTicketsPage.state('ticketPackage');

expect(seatArray[2][0].sold).to.be.true; expect(seatArray[2][1].sold).to.be.true; expect(ticketPackage.seats).to.have.lengthOf(0); });

ex6-mocha-enzyme-es6-react-router/src/Tickets/BuyTicketsPage.test.js (excerpts, 6 of 6)

Ex7: Mocha Enzyme, ES6, React-Router, and Redux

• Mocha test framework

• JSDom

• Enzyme

• Chai’s expect function

• Sinon for function spies

• React-Router

• Redux

Ex7: Redux folder structure!"" src#   !"" actions#   #   !"" actionTypes.js#   #   !"" castMemberActions.js#   #   !"" seatActions.js#   #   !"" seatActions.test.js#   #   !"" userActions.js#   #   $"" userActions.test.js#   !"" components#   !"" reducers#   #   !"" castMembersReducer.js#   #   !"" castMembersReducer.test.js#   #   !"" index.js#   #   !"" initialState.js#   #   !"" initialState.test.js#   #   !"" seatReducer.js#   #   !"" seatReducer.test.js#   #   !"" userReducer.js#   #   $"" userReducer.test.js#   !"" selectors#   #   !"" selectors.js#   #   $"" selectors.test.js#   !"" store#   #   $"" configureStore.js!"" test#   !"" fixtures#   #   !"" mockCastMemberData.js#   #   $"" mockSeatData.js

Ex7: What a Test Looks Likeimport { expect } from 'chai';import seatReducer from './seatReducer';import * as actions from '../actions/seatActions';import mockSeatData from '../../test/fixtures/mockSeatData';import * as types from '../actions/actionTypes';

describe('Seat reducer', function() { let seats; beforeEach(function() { seats = JSON.parse(JSON.stringify(mockSeatData)); });

it('should load seats', function() { const initialState = []; const action = actions.loadSeats(seats);

const newState = seatReducer(initialState, action); const expectedSeats = JSON.parse(JSON.stringify(mockSeatData));

expect(newState).to.equal(seats); });

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.test.js (part 1 of 3)

Ex7: What a Test Looks Like

it('should mark a seat as selected', function() { const initialState = seats; const seat = initialState[0][2]; // mock seat A3

const expectedState = JSON.parse(JSON.stringify(initialState)); expectedState[0][2].selected = true;

const action = actions.toggleSeatSelected(seat);

const newState = seatReducer(initialState, action);

// .eql instead of .equal, // to compare the contents, instead of the reference expect(newState).to.eql(expectedState); });

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.test.js (part 2 of 3)

Ex7: What a Test Looks Like it('should mark a seat as sold', function() { const initialState = seats; const seat = seats[0][2]; // mock seat A3 seat.selected = true;

const expectedState = JSON.parse(JSON.stringify(seats)); expectedState[0][2].sold = true; expectedState[0][2].selected = false;

const action = actions.markSeatSold(seat);

const newState = seatReducer(initialState, action);

// .eql instead of .equal, // to compare the contents, instead of the reference expect(newState).to.eql(expectedState); });});

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.test.js (part 3 of 3)

Ex7: The Code Under Test

import * as types from '../actions/actionTypes';import initialState from './initialState';

export default function seatReducer(state = initialState.seats, action) { let newState;

switch(action.type) { case types.LOAD_SEATS: return action.seats;

break;

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.js (part 1 of 3)

Ex7: The Code Under Test

case types.TOGGLE_SEAT_SELECTED: newState = state.map((row) => { return row.map((seat) => { if (seat.seatNumber === action.seat.seatNumber) { const updatedSeat = Object.assign({}, seat); updatedSeat.selected = !updatedSeat.selected; return updatedSeat; } else { return seat; } }); }); return newState; break;

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.js (part 2 of 3)

Ex7: The Code Under Test case types.MARK_SEAT_SOLD: newState = state.map((row) => { return row.map((seat) => { if (seat.seatNumber === action.seat.seatNumber) { const updatedSeat = Object.assign({}, seat); updatedSeat.selected = false; updatedSeat.sold = true; return updatedSeat; } else { return seat; } }); }); return newState; break;

default: return state; }}

ex7-mocha-enzyme-es6-react-router-redux/src/reducers/seatReducer.js (part 3 of 3)

Resources• https://facebook.github.io/react/docs/test-utils.html 🚫

• https://github.com/airbnb/enzyme

• https://semaphoreci.com/community/tutorials/testing-react-components-with-enzyme-and-mocha#comments

• https://facebook.github.io/jest/

• https://egghead.io/courses/react-testing-cookbook

Contact

• @vanwilson on Twitter

[email protected]

• vanwilson.info (where I blog once or twice a year)

Questions?

Go forth, and test.