TDD+React
Will Vaughn

Software Engineer R/GA
@nackjicholsonn
github:nackjicholson
willieviseoae@gmail.com
william.vaughn@rga.com
TDD+React
- Designing Components
- Test Anatomy 101
- React ecosystem testing libs
- Test drive an easy component
- Talk about stuff which is hard to test
Designing Components
Big components will hurt.
>3 Dependencies will crush you.
How will we possibly deal with all the well organized, carefully named, comprehensible small things?!
Designing Components
Data => Display
Interaction => Action
Designing Components
Test Anatomy 101
import assert from 'assert';
describe('What unit aspect are you testing?', () => {
it('What should the unit do?', () => {
const actual = 'What is the actual output?';
const expected = 'What do you expect the output to be?';
assert.equal(actual, expected, 'Describe to me what happened here.');
});
});5 Questions
Test Anatomy 101
import assert from 'assert';
import shout from './shout';
describe('shout', () => {
it('should uppercase a string', () => {
// "Arrange"
const str = 'foobar';
// "Act"
const actual = shout(str);
const expected = 'FOOBAR';
// "Assert"
assert.equal(actual, expected, 'returns str uppercased');
});
});shout should uppercase a string
Testing Libararies
- airbnb/enzyme
- React Addons Test Utilities
- jquense/teaspoon
airbnb/enzyme
- Shallow, static, full rendering
- Traversal helpers
- Simulated events
- A bit clunky
- Issues with nested DOM elements
- Issues with simulated events
ReactTestUtils
- Official facebook support
- Makes tests really verbose
- Readability suffers
- Light documentation
jquense/teaspoon
- Full and shallow render
- Fluent jQuery-like API
- Nested DOM elements
- Simulated events
- Lesser known library
Presentational Component
Does it display the props we give it?
Does it respond to user interactions?
Stateless Functional Component
SelectControl

SelectControl HTML
<div id="robot-sounds-wrapper">
<label for="robot-sounds-select">Robot Sounds:</label>
<select id="robot-sounds-select" name="robot-sounds-select">
<option value="beep">beep</option>
<option value="boop" selected >boop</option>
</select>
</div>{
id: 'robot-sounds'
labelText: 'Robot Sounds:'
defaultValue: 'boop',
onSelection: (value) => { /* do something with value */ }
}Props
HTML
SelectControl Outline
import $ from 'teaspoon';
import assert from 'assert';
import React from 'react';
import { spy } from 'sinon';
import SelectControl from './SelectControl';
describe('app/components/SelectControl', () => {
it('should render wrapper div');
it('should render label with labelText prop');
it('should render children prop within select list');
it('should be able to select a default option');
it('should handle selection changes via onSelection callback prop');
});SelectControl Wrapper
it('should render wrapper div', () => {
// "Arrange"
const props = { id: 'foo' };
// "Act"
const actual = $(<SelectControl {...props} />)
.render()
.find('div[id=foo-wrapper]')
.length;
const expected = 1;
// "Assert"
assert.equal(actual, expected, 'rendered 1 wrapper div based on id prop');
});SelectControl Wrapper
import React from 'react';
export default function SelectControl(props) {
return <div id={`${props.id}-wrapper`}></div>;
}SelectControl <label>
it('should render label with labelText prop', () => {
const props = { id: 'foo', labelText: 'test.labelText' };
const actual = $(<SelectControl {...props} />)
.render()
.find('div > label[htmlFor=foo-select]')
.text();
const expected = 'test.labelText';
assert.equal(actual, expected, 'label rendered with text from labelText prop');
});SelectControl <label>
import React from 'react';
export default function SelectControl(props) {
return (
<div id={`${props.id}-wrapper`}>
<label htmlFor={`${props.id}-select`}>{props.labelText}</label>
</div>
);
}SelectControl children
it('should render children prop within select list', () => {
const props = { id: 'foo' };
const component = (
<SelectControl {...props}>
<option value="alpha.value">alpha.text</option>
<option value="bravo.value">bravo.text</option>
<option value="charlie.value">charlie.text</option>
</SelectControl>
);
const $options = $(component)
.render()
.find('div > select[id=foo-select] option');
const actual = $options
.map(optionNode => [optionNode.value, optionNode.text])
.get();
const expected = [
['alpha.value', 'alpha.text'],
['bravo.value', 'bravo.text'],
['charlie.value', 'charlie.text']
];
assert.deepEqual(actual, expected, 'rendered option children within select list');
});SelectControl children
import React from 'react';
export default function SelectControl(props) {
return (
<div id={`${props.id}-wrapper`}>
<label htmlFor={`${props.id}-select`}>{props.labelText}</label>
<select id={`${props.id}-select`} name={`${props.id}-select`}>
{props.children}
</select>
</div>
);
}SelectControl children
import React from 'react';
export default function SelectControl(props) {
const { id, children, labelText } = props;
const wrapperId = `${id}-wrapper`;
const selectId = `${id}-select`;
return (
<div id={wrapperId}>
<label htmlFor={selectId}>{labelText}</label>
<select id={selectId} name={selectId}>
{children}
</select>
</div>
);
}
SelectControl defaultValue
it('should be able to select a default option', () => {
const props = { id: 'foo', defaultValue: 'bravo.value' };
const component = (
<SelectControl {...props}>
<option value="alpha.value">alpha.text</option>
<option value="bravo.value">bravo.text</option>
<option value="charlie.value">charlie.text</option>
</SelectControl>
);
const $options = $(component)
.render()
.find('div > select[id=foo-select] option');
const checkedOption = $options
.get()
.find(optionNode => optionNode.selected);
const actual = checkedOption.value;
const expected = 'bravo.value';
assert.equal(actual, expected, 'selected the option via defaultValue prop');
});SelectControl defaultValue
import React from 'react';
export default function SelectControl(props) {
const { id, children, defaultValue, labelText } = props;
const wrapperId = `${id}-wrapper`;
const selectId = `${id}-select`;
return (
<div id={wrapperId}>
<label htmlFor={selectId}>{labelText}</label>
<select id={selectId} name={selectId} defaultValue={defaultValue}>
{children}
</select>
</div>
);
}
SelectControl onSelection
it('should handle selection changes via onSelection callback prop', () => {
const props = { id: 'foo', onSelection: spy() };
$(<SelectControl {...props} />)
.render()
.find('div > select[id=foo-select]')
.trigger('change', { target: { value: 'bingo' } });
assert(props.onSelection.calledOnce, 'onSelection callback prop called once');
const actual = props.onSelection.args[0];
const expected = ['bingo'];
assert.deepEqual(
actual,
expected,
'onSelection callback called with change value'
);
});SelectControl onSelection
import React from 'react';
export default function SelectControl(props) {
const { id, children, defaultValue, labelText, onSelection } = props;
const wrapperId = `${id}-wrapper`;
const selectId = `${id}-select`;
function handleChange(event) {
onSelection(event.target.value);
}
return (
<div id={wrapperId}>
<label htmlFor={selectId}>{labelText}</label>
<select
id={selectId}
name={selectId}
defaultValue={defaultValue}
onChange={handleChange}
>
{children}
</select>
</div>
);
}
Up the difficulty!
Thank You!
@nackjicholsonn
TDD+React
By Will Vaughn
TDD+React
Outlined approach to testing react components with an emphasis on TDD.
- 1,402