When it comes to integration testing, Javascript frontends are notoriously hard to test — there’s lots of setup required, and you often need to rely on clunky simulations of user interactions like clicks and keyboard events.

React’s virtual DOM offers some interesting opportunities for simplifying the process of testing, and Airbnb’s Enzyme framework makes testing your components an absolute pleasure. We recently tried out Enzyme on a React project at Brewhouse. We were blown away by how much simpler it was to write tests, and we’re never looking back.

It’s not the only option, of course. React itself offers the ReactTestUtils library - in fact, Enzyme depends on this for its integration with React. But Enzyme’s jQuery-style approach to node selection and manipulation makes it super familiar and really easy to work with.

Let’s investigate how we can put Enzyme to work. To demonstrate some of its features, I put together a small little app called Gif Grabbr. It uses the GIPHY API to find random GIFs based on your search. Take it for a spin first, then come back here to check out some code.

Gif Grabbr

(Note: In the examples below I’m using Mocha with Chai and Karma as the test runner, but Enzyme can be used with pretty much any testing setup. Have a look at the Gif Grabbr source to see more details about my setup.)

Shallow Rendering

Enzyme’s biggest strength is its shallow rendering capabilities. Not only does this encourage you to limit the scope of your tests to a single component, it also prevents errors deep in your node tree from affecting all your tests, and removes the requirement to mock a ton of objects.

Consider the App component in GIF Grabbr, which is the root component within which everything else is rendered:

// components/app.js

// ...

class App extends React.Component {

  // ...

  render () {
    return (
      <div style={styles}>
        <h1>gif grabbr</h1>
        <p>find a GIF on <a href='http://giphy.com'>giphy</a>. Keep pressing enter for more results.</p>
        <div>
          <SearchBar onSearch={this.handleSearch}/>
        </div>
        <ImageDisplay
          loading={this.state.loading}
          url={this.state.gif.url}
          width={this.state.gif.width}
          sourceUrl={this.state.gif.sourceUrl}
          />
      </div>
    );
  }
}

To ensure the stability of my tests, as well as for performance reasons, I can use .shallow() to render this component only one level deep:

const wrapper = shallow(<App />);
 

This gives me a ShallowWrapper object which I can do all sorts of fun stuff with. To illustrate how Enzyme is handling this render, let’s use .debug() to take a look at the output:

wrapper.debug()

"<div style=>
<h1>
gif grabbr
</h1>
<p>
find a GIF on
<a href="http://giphy.com">
giphy
</a>
. Keep pressing enter for more results.
</p>
<div>
<SearchBar onSearch={[Function]} />
</div>
<ImageDisplay loading={true} url={[undefined]} width={[undefined]} sourceUrl={[undefined]} />
</div>"

.debug() returns a HTML-ish string representing the rendered output. Notice that the shallow render didn’t touch the SearchBar or ImageDisplay components, but it did pass in any props that were available at the time. In our tests, this allows us to verify that a child component would have received the correct props without needing to actually render the thing.

// components/__tests__/imageDisplay.test.js

// ...

it('Passes loading:true to ImageDisplay', () => {
  const wrapper = shallow(<App />);
  const imageDisplay = wrapper.find('ImageDisplay');
  assert.equal(imageDisplay.prop('loading'), true);
});

Put the ‘$’ back in DOM traversal

Enzyme’s three flavours of rendering (shallow, full, and static) all return wrapper objects which give you jQuery-style power over your node tree. No longer must you wade through a jungle of nested arrays in order to test some deep-level node:

// components/imageDisplay.js
class ImageDisplay extends React.Component {

  // ...

  render () {
    const url = this.props.loading ? 'loading.gif' : this.props.url;
    const width = this.props.width || 200;
    return (
      <div style={styles}>
        <a href={this.giphySourceUrl()} title='view this on giphy' target='new'>
          <img id='gif' src={url} width={width} />
        </a>
      </div>
    );
  }
// components/__tests__/imageDisplay.test.js

// ...

describe('ImageDisplay', () => {
  it('Renders the loading gif at the default width when passed loading:true', () => {
    const props = {
      loading: true
    };
    const wrapper = shallow(<ImageDisplay {...props}/>);
    const image = wrapper.find('img');

    assert.equal(image.prop('src'), LOADING_GIF);
    assert.equal(image.prop('width'), DEFAULT_WIDTH);
  });

  // ...

}

Using the wrapper I get from .shallow(), I can just search for the image tag instead of needing to know exactly where it is in the node tree. This encourages a more declarative form of testing, minimizing your tests’ reliance on a specific tree structure.

Enzyme Selectors FTW

The selector is more than meets the eye. In addition to CSS-style selectors, an Enzyme Selector can find nodes by their component constructor, display name, even object properties:

// components/imageDisplay.js

// ...

class ImageDisplay extends React.Component {

  // ...

  render () {

    // ...

    return (
      <div style={styles}>
        <a href={this.giphySourceUrl()} title='view this on giphy' target='new'>
          <img id='gif' src={url} width={width} />
        </a>
      </div>
    );
  }
}
// components/__tests__/imageDisplay.test.js

// ...

it('Renders an image when passed a url', () => {
  const props = {
    url: 'http://www.example.com/pusheen.gif'
  }
  const wrapper = shallow(<ImageDisplay {...props}/>);
  const image = wrapper.find({src: 'http://www.example.com/pusheen.gif'});
  assert.equal(image.length, 1);
});

Simulations

Enzyme gives you a concise and elegant way of simulating user events, one of the trickier aspects of UI testing. Just pass the name of the event you want to simulate, along with any required data:

// components/__tests__/searchBar.test.js"

// ...

  it('Triggers a new search as the user types', () => {
    const onSearchStub = sinon.spy();
    const wrapper = shallow(<SearchBar onSearch={onSearchStub}/>);
    const searchField = wrapper.find('input');
    const event1 = {target: {value: 'cat'}};
    const event2 = {target: {value: 'cats'}};

    searchField.simulate('change', event1);
    searchField.simulate('change', event2);
    assert.equal(onSearchStub.calledTwice, true);
  });

...

It’s also worth noting that the event argument can be anything you want. Unlike ReactTestUtils, you’re not limited to only events that React understands. Following convention, you could call wrapper.simulate('donut') and Enzyme would simply look for an onDonut event handler. Pretty neat.

Conclusion

Enzyme is an elegant and powerful way of testing your React applications. It can significantly increase the brevity of your tests thanks to its selector engine, and its approach to event simulation is quite promising. It also plays nicely with most test suites and assertion libraries out there, so you probably don’t need to change your workflow to start using it today.

Hit up the Enzyme docs to learn more. You should also check out Leland Richardson’s Lightning Talk at ReactConf 2016 to get a good overview of the problems Enzyme solves.

Let's Work Together

Find out why our transparent, collaborative process is the best way to make well-loved products.

Get in touch today
comments powered by Disqus