The document discusses best practices for testing React applications, emphasizing the importance of automated testing and providing insights into methodologies like test-driven development. It outlines various types of testing such as unit, integration, and functional testing, and introduces tools and libraries—including Mocha, Jest, and Enzyme—necessary for effectively testing React components. The document also includes a bare-bones setup for testing environments and examples of testing React components with different strategies.
This section introduces the topic of testing in React and identifies the audience's qualifications and goals.
Discusses how developers currently test, emphasizes the significance of writing automated tests and outlines the benefits of testing, highlighting its limitations.
Introduces Test-Driven Development (TDD) and Test-During Development (TDD), explaining the processes and cycles involved in writing tests and code.
Explains the different kinds of automated testing: Unit Testing, Integration Testing, and Functional Testing.
Describes why testing in React is manageable due to its predictable nature, equating components to mathematical functions.
Discusses potential confusion with multiple libraries and outlines necessary files and dependencies for setting up React testing.
Overview of various test frameworks (Jasmine, Jest, Mocha) and assertion libraries (Chai, Expect) used in React.
Explores available options for a testing DOM, including real browsers and JSDom, and mentions helper libraries like React Test Utils and Enzyme.
Emphasizes where tests should be placed in the project structure, leading to practical examples and an example app repository.
Presents example applications in various styles and frameworks (ES5, Mocha, React Test Utils), highlighting test setups and structures. Introduces Enzyme as an enriched tool over React Test Utils, examining how it aids in component testing.
Explores Jest’s evolution and advantages, including its testing capabilities in combination with React applications.
Discusses the integration of Redux alongside Mocha and Enzyme for testing, focusing on the structure and capabilities of Redux.
Provides additional resources for further learning, personal contact information, and encouragement for implementing testing.
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
6.
How most devsactually test
(Look closely; she’s hitting CTRL-R over and over again.)
Benefits of Testing
Testingdoes …
➡ … 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.
10.
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.
11.
“It is aprofoundly 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
12.
Test-Driven Development
FAIL
PASSREFACTOR
1. WriteTests
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)
13.
Test-During Development
CODE
FAILPASS
1. WriteSome 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)
14.
Kinds of automatedtesting
• Unit Testing
• Integration Testing
• Functional / Acceptance Testing
15.
Kinds of automatedtesting
Unit
Integration
Functional
xUnit
Test Harness
Selenium
16.
Kinds of automatedtesting
• Unit Testing
• Integration Testing
• Functional / Acceptance Testing
17.
Why testing Reactis easy
React apps are meant to be
predictable,
and consequently they are eminently
testable.
18.
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
An aside: It’sstill 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)
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
30.
How to decide?Just ask?
https://twitter.com/housecor/status/717759300886618112
31.
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
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
34.
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
35.
Options for atest 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.
36.
Options for atest DOM
• A real browser (Chrome, Firefox, Safari etc)
• PhantomJS (a “headless” browser)
• JSDom
npm install —save-dev jsdom
37.
Helper Libraries
• ReactTest Utils - from Facebook, bare-bones
• React Shallow Testutils - some helper wrappers
• Enzyme - like jQuery, but for tests
• Sinon - spies, mocks, and stubs
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)
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
49.
Ex2: What changedin
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)
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()
54.
Ex2: What aTest 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)
55.
Ex2: What aTest Looks Like
describe('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)
56.
Ex2: What aTest 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)
57.
99 Problems withReact 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?
58.
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)
60.
Enzyme
• Extends ReactTest 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: What aTest 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)
64.
Ex3: What aTest 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)
65.
Ex3: What aTest 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)
66.
Ex3: What aTest 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)
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
71.
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: What aTest 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)
74.
Ex5: What aTest 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)
75.
One big advantagefor Jest
Jest comes set up when you create a
React app with
create-react-app,
Facebook’s React generator
76.
Ex6: Mocha Enzyme,ES6,
and React-Router
• Mocha test framework
• JSDom
• Enzyme
• Chai’s expect function
• Sinon for function spies
• React-Router
Ex6: What aTest 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)
80.
Ex6: What aTest 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)
81.
Ex6: What aTest 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 aTest 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)
84.
Ex6: What aTest 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)
85.
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: What aTest Looks Like
import { 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)
88.
Ex7: What aTest 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)
89.
Ex7: What aTest 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)
90.
Ex7: The CodeUnder 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)
91.
Ex7: The CodeUnder 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)