Enzyme
NYC Bootcamper's Anonymous Meetup
August 6th, 2018 @ American Express
Tommy York, http://www.tommy-york.com
Summary
- Testing utility, designed for React
- Mimicks jQuery's API for DOM manipulation and traversal
- Unopinionated about your test runner or library:
chai-enzyme
,jasmine-enzyme
,jest-enzyme
,should-enzyme
, andexpect-enzyme
- Already included in
create-react-app
!
Setup
1
tyork% npm i --save-dev enzyme enzyme-adapter-react-16
Then:
1
import Enzyme from 'enzyme';
2
import Adapter from 'enzyme-adapter-react-16';
3
4
Enzyme.configure({ adapter: new Adapter() });
Usage
Common Usage
- Shallow Rendering
- Full DOM Rendering
- Static Rendered Markup
Plus:
- Testing static methods on the class
- Testing methods on a rendered component
- More!
The Component
I wrote tests for a tooltip component we use to render a wide range of content for different Canadian AMEX Cards.
Setting up some test props
1
const props = {
2
children: <div>
3
<span>Example</span>
4
<span>Children</span>
5
</div>,
6
clickableText: 'Clickable text.',
7
tooltipPopupText: 'Tooltip text.',
8
className: '',
9
styles,
10
};
And testing our component...
1
const shallowTooltip = shallow(<Tooltip {...props} />);
2
3
const mountedTooltip = mount(<Tooltip {...props} />);
4
5
const staticallyRenderedTooltip =
6
render(<Tooltip {...props} />);
Types of Rendering
- Shallow
- Full DOM
- Static
Shallow Rendering
Shallow rendering takes the result of the component's render
method, and gives us a wrapper
object, which allows us to do basic traversal.
However, Kent C. Dodds' take:
With shallow rendering, I can refactor my component's implementation and my tests break. With shallow rendering, I can break my application and my tests say everything's still working.
Shallow Rendering, Pt. 2
You don't get:
- React lifecycle methods (
componentDidMount
,componentWillReceiveProps
, etc.) - The ability to interact with DOM elements
- The react elements within the component - you only render one component deep.
Shallow Render, Pt. 3
Avoid the temptation to solely write tests for methods, without also testing for the actual user interaction that triggers those methods.
Full DOM Rendering / mount
ing
- JSDOM is a simple JavaScript browser
mount
API requires a DOM, so JSDOM is used when there's no browser environment (Node!).
Static Rendering
Enzyme also supports rendering your component to HTML, then traversing it with the HTML traversal library Cheerio
.
Like JSDOM
, Cheerio
does not produce a visual rendering. However, unlike JSDOM
, Cheerio does not:
- Apply CSS
- Load external resources
- Execute JavaScript
Test Coverage
Among other things, we're going to want to test a range of functionality, including:
- Snapshots
- User interaction with the component
- Any static methods on the tooltip
- The click event listener added to the window, used to close the tooltip when users click outside of the tooltip.
Snapshots
We can test our component with a saved "snapshot" from a previous test run.
1
it('should match its snapshot', () => {
2
const tooltip = mount(<Tooltip {...props} />);
3
expect(tooltip).toMatchSnapshot();
4
});
You can update the snapshot from the command line if you've decided it should change.
User Interaction (using mount
)
1
it('should open / close', () => {
2
const tooltip = mount(<Tooltip {...props} />);
3
4
5
tooltip.first('span').simulate('click');
6
expect(tooltip.state('visible')).toBe(true);
Cool Functionality
Test Class Methods
If you're using Jest and Enzyme, you can use jest.spyOn
to test function calls, much like you would with Sinon.js
.
1
const tooltip = mount(<Tooltip {...props} />);
2
3
jest.spyOn(
4
Tooltip.instance(),
5
'addResizeListener'
6
);
7
8
tooltip.first('span').simulate('click');
9
expect(spyOnAdd).toBeCalled();
Test window events
We can test window events as well, using standard window properties (innerWidth
and innerHeight
) and our ability to send arbitrary events through JSDOM
:
1
window.resizeTo = (width, height) => {
2
window.innerWidth = width;
3
window.innerHeight = height;
4
window.dispatchEvent(new Event('resize'));
5
};