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, and expect-enzyme
  • Already included in create-react-app!

Setup

1
tyork%  npm i --save-dev enzyme enzyme-adapter-react-16

Then:

1
2
3
4
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

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.

Example


Setting up some test props

1
2
3
4
5
6
7
8
9
10
const props = {
children: <div>
<span>Example</span>
<span>Children</span>
</div>,
clickableText: 'Clickable text.',
tooltipPopupText: 'Tooltip text.',
className: '',
styles,
};


And testing our component...

1
2
3
4
5
6
const shallowTooltip = shallow(<Tooltip {...props} />);

const mountedTooltip = mount(<Tooltip {...props} />);

const staticallyRenderedTooltip =
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 / mounting

  • 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
2
3
4
it('should match its snapshot', () => {
const tooltip = mount(<Tooltip {...props} />);
expect(tooltip).toMatchSnapshot();
});

You can update the snapshot from the command line if you've decided it should change.


User Interaction (using mount)

1
2
3
4
5
6
it('should open / close', () => {
const tooltip = mount(<Tooltip {...props} />);


tooltip.first('span').simulate('click');
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
2
3
4
5
6
7
8
9
const tooltip = mount(<Tooltip {...props} />);

jest.spyOn(
Tooltip.instance(),
'addResizeListener'
);

tooltip.first('span').simulate('click');
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
2
3
4
5
window.resizeTo = (width, height) => {
window.innerWidth = width;
window.innerHeight = height;
window.dispatchEvent(new Event('resize'));
};