Tag Archives: testing

Testing React components with Jest

Jest makes a bold claim to be “Painless Javascript Unit Testing”. I’m looking forward to using it, but it hasn’t quite lived up to that claim for me yet. Hopefully this post will help it do so for you.

If you just want a finished example, check out the tests in react-dropdown-input.

Choose the right version of node and jest

On my Mac, the latest version of jest (0.4.0) doesn’t run with the latest version of node (0.12.0). With node 0.10.37, it only works intermittently – sometimes saying:

dyld: lazy symbol binding failed: Symbol not found: _node_module_register
Expected in: dynamic lookup

and sometimes saying:

Error: Worker process exited before responding! exit code: null, exit signal: SIGSEGV (or SIGTRAP)
A worker process has quit unexpectedly! This is bad news, shutting down now!

The solution is to use jest version 0.2.1 with node 0.10.x. Hopefully future versions of jest will overcome this.

Note that the latest version of Babel seems to require node 0.12.x.

jest.dontMock is not recursive

I am testing a component called DropdownInput, built on top of React-Bootstrap, eg for DropdownInput.js:

    var ReactBootstrap = require('react-bootstrap');
    var Input = ReactBootstrap.Input;
    var DropdownMenu = ReactBootstrap.DropdownMenu;
    // in render function: 
      return (
        <div>
          <Input .... />
          <DropdownMenu 
            className={this.props.menuClassName}
            ....>
          </DropdownMenu>
        </div>);

I wrote a test like this (in __tests__.DropdownInput-test.js):

    jest.dontMock('../DropdownInput');
    var React = require('react/addons');
    var DropdownInput = require('../DropdownInput');
    var TestUtils = React.addons.TestUtils;
    
    describe('DropdownInput', function() {
      it('contains the test class', function() {
        var elt = (<DropdownInput menuClassName='test'/>);
        var renderedItem = TestUtils.renderIntoDocument(elt);
        TestUtils.findRenderedDOMComponentWithClass(
                                renderedItem, 'test');
      });
    });

This could not find any DOM elements with the class test:

Error: Did not find exactly one match (found: 0) for class:test

The reason is that while jest.dontMock('../DropdownInput') uses the real DropdownInput, it does not use the real ReactBootstrap components. Which is fair enough. So you need to add:

jest.dontMock('react-bootstrap');

But that’s not enough! The reason is that ReactBootstrap contains the line require('classnames'), which help it put class names on its components; but jest is still mocking classnames. So you need to also add:

jest.dontMock('classnames');

Happily, that does the job. It feels fragile though, because I don’t care what packages ReactBootstrap uses, as long as it works. If the author of ReactBootstrap changes which packages it uses, I don’t want all my tests breaking! Is there a better way?

There is a discussion of the pros and cons of jest’s approach to mocking here.

Shallow and deep rendering

There’s a good argument that your component tests should not try to test the subcomponent behavior anyway. In that case, you might be happy with a ‘shallow’ test like this:

    // __tests__/DropdownInput-shallow-test.js
    //
    // Check it has the right components
    //

    jest.dontMock('../DropdownInput');
    var React = require('react/addons');
    var DropdownInput = require('../DropdownInput');
    var ReactBootstrap = require('react-bootstrap');
    var TestUtils = React.addons.TestUtils;
    
    describe('DropdownInput', function() {
      var menuClassName = 'test';
      var elt = (<DropdownInput menuClassName={menuClassName}/>);
      var result;

      beforeEach(function() {
        var shallowRenderer = TestUtils.createRenderer();
        shallowRenderer.render(elt);
        result = shallowRenderer.getRenderOutput();
      });

      it('contains the right components', function() {
        // for now, assume in that order - TODO: generalize
        var child0 = result.props.children[0];
        var child1 = result.props.children[1];
        expect(child0.type).toEqual(ReactBootstrap.Input);
        expect(child1.type).toEqual(ReactBootstrap.DropdownMenu);
        // and check the menu is passed the right class name
        expect(child1.props.className).toEqual(menuClassName);
      });

      it('contains the right first menu item', function() {
        // again, not a very general approach
        var child1 = result.props.children[1];
        var child1props = child1.props.children[0];
        expect(child1props[0].props.children).toContain(names[0]);
      });

    });

And this works nicely. However, as it is written, it assumes a lot about the structure of the component, like what order the elements are in. If I decide to wrap my component in an extra div for some reason, it will break my tests. Is there an equivalent to TestUtils.findRenderedDOMComponentWithType that we can use for shallow rendering, so I don’t have to assume ordering?

But you could also argue the opposite case: that the tests shouldn’t care how DropdownInput produces the desired behavior, only that it does – ie. it should test the behavior, not which components it uses. For this you need a ‘deep’ test, with lots of the mocking turned off. Eg:

    // __tests__/DropdownInput-deep-test.js
    //
    // Check it has the right behavior
    //
    jest.dontMock('../DropdownInput');
    jest.dontMock('react-bootstrap');
    jest.dontMock('classnames');
    var React = require('react/addons');
    var DropdownInput = require('../DropdownInput');
    var ReactBootstrap = require('react-bootstrap');
    var TestUtils = React.addons.TestUtils;

    describe('DropdownInput', function() {
      var menuClassName = 'test';
      var elt = (<DropdownInput menuClassName={menuClassName}/>);
      var renderedItem;

      beforeEach(function() {
        renderedItem = TestUtils.renderIntoDocument(elt);
      });

      it('has a working input', function() {
        var txt = 'a';
        var input = TestUtils.findRenderedDOMComponentWithTag(
                        renderedItem, 'input').getDOMNode();
        //TestUtils.Simulate.keyDown(input, {key: 'a'});  // this doesn't work
        TestUtils.Simulate.change(input,  {target: {value: txt}});
        expect(input.value).toEqual(txt);
      });

    });

And this works nicely too, except that the test needs to know which packages to unmock. (And why doesn’t Simulate.keyDown work?)

So, at this early stage in my jest career, I plan to include both sorts of tests for each component, in separate files.

Flux components

If you’re using the Flux architecture, then user interaction will often fire an action, rather than directly changing the DOM. So instead of testing the DOM directly, we need to test that an action was sent. To do this, I adapted the tutorial on Testing Flux Applications (which describes testing stores), as follows:

    var element,
        cpt,
        rendered,
        ActionCreators,
        MyComponent;

    describe('MyComponent', function() {

        beforeEach(function() {
          // by requiring a new copy for every test, 
          // we reset the mock call counters for each test
          MyComponent = require('../my-component.js');
          ActionCreators = require('../../../actions/action-creators');
          // Render it
          element = <MyComponent />;  // include relevant props
          cpt = TestUtils.renderIntoDocument(element);
          rendered = TestUtils.findRenderedDOMComponentWithTag(cpt, 'div');
          // note - this version doesn't work - why??
          // rendered = TestUtils.findRenderedComponentWithType(cpt, MyComponent);
        });

        it('clicking on it sends the right action', function() {
          // first check that no action calls were made yet
          // ActionCreators.myAction is the desired action
          var actionCalls = ActionCreators.myAction.mock.calls;
          expect(actionCalls.length).toEqual(0);
          // Simulate a click and verify that it sends out the action
          TestUtils.Simulate.click(rendered);
          expect(actionCalls.length).toEqual(1);
          // check it sent the right params (replace ... with args)
          expect(ViewActionCreators.myAction).toBeCalledWith(...);
        });
    });

(You won’t find this in the DropdownInput component tests, since it doesn’t fire any actions.)

Conclusion

I hope this saves you some time and development pain. Please let me know if you have better solutions to any of these issues!

The working DropdownInput component and tests are available at github.com/RacingTadpole/react-dropdown-input; you can see also a demo here.