Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing a React Modal component

I'm sorry, but I've been having the toughest time trying to test closing my React Modal by clicking a button. The Modal is as simple as can be, and I've tried everything I can think of or find, but I still can't query its children.

The Modal component:

var React = require('react');
var Modal = require('react-bootstrap').Modal;
var Button = require('react-bootstrap').Button;

var MyModal = React.createClass({
  ...
  render: function() {
    return (
      <Modal className="my-modal-class" show={this.props.show}>
        <Modal.Header>
          <Modal.Title>My Modal</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          Hello, World!
        </Modal.Body>
        <Modal.Footer>
          <Button onClick={this.props.onHide}>Close</Button>
        </Modal.Footer>
      </Modal>
    );
  }
});

My goal is to test if that Close button fires the onHide() function when it is clicked.

My test file:

describe('MyModal.jsx', function() {
  it('tests the Close Button', function() {
    var spy = sinon.spy();
    var MyModalComponent = TestUtils.renderIntoDocument(
      <MyModal show onHide={spy}/>
    );

    // This passes
    TestUtils.findRenderedComponentWithType(MyModalComponent, MyModal);

    // This fails
    var CloseButton = TestUtils.findRenderedDOMComponentWithTag(MyModalComponent, 'button');

    // Never gets here
    TestUtils.Simulate.click(CloseButton);
    expect(spy.calledOnce).to.be.true;
  });
});

No matter what I try, I can't seem to find the Close Button.

like image 573
user1778856 Avatar asked Jan 29 '16 13:01

user1778856


2 Answers

I wrote a jsFiddle using the React Base Fiddle (JSX) to find out what was going on in your test (I created my own 'spy' that simply logged to the console when called).

I found out that the reason you can't find your button is because it doesn't exist where you might expect it to.

The Bootstrap Modal Component (<Modal/>) is actually contained within a React-Overlays modal component (called BaseModal in the code, which is from here). This in turn renders a component called Portal whose render method simply returns null. It is this null value you are trying to find rendered components on.

Due to the modal not being rendered the traditional React way, React cannot see the modal in order to use TestUtils on. An entirely seperate <div/> child node is placed in the document body and this new <div/> is used to build the modal.

So, in order to allow you to simulate a click using React's TestUtils (the click handler on the button is still bound to the button's click event), you can use standard JS methods to search the DOM instead. Set up your test as follows:

describe('MyModal.jsx', function() {
  it('tests the Close Button', function() {
    var spy = sinon.spy();
    var MyModalComponent = TestUtils.renderIntoDocument(
      <MyModal show onHide={spy}/>
    );

    // This passes
    TestUtils.findRenderedComponentWithType(MyModalComponent, MyModal);

    // This will get the actual DOM node of the button
    var closeButton = document.body.getElementsByClassName("my-modal-class")[0].getElementsByClassName("btn btn-default")[0];

    // Will now get here
    TestUtils.Simulate.click(CloseButton);
    expect(spy.calledOnce).to.be.true;
  });
});

The function getElementsByClassName returns a collection of elements with that class so you must take the first one (and in your test case, your only one) from each collection.

Your test should now pass ^_^

like image 106
Martoid Prime Avatar answered Nov 19 '22 18:11

Martoid Prime


This is the solution I ended up going with. It stubs the way React renders a Modal to just render a <div> instead.

test-utils.js:

var sinon = require('sinon');
var React = require('react');
var Modal = require('react-bootstrap').Modal;

module.exports = {
  stubModal: function() {
    var createElement = React.createElement;
    modalStub = sinon.stub(React, 'createElement', function(elem) {
      return elem === Modal ?
        React.DOM.div.apply(this, [].slice.call(arguments, 1)) :
        createElement.apply(this, arguments);
    });
    return modalStub;
  },
  stubModalRestore: function() {
    if (modalStub) {
      modalStub.restore();
      modalStub = undefined;
    } else {
      console.error('Cannot restore nonexistent modalStub');
    }
  }
};

modal-test.jsx

var testUtils = require('./test-utils');

describe('test a Modal!', function() {
  before(testUtils.stubModal);
  after(testUtils.stubModalRestore);

  it('renders stuff', function() {
    var MyModalComponent = TestUtils.renderIntoDocument(
      <MyModal show/>
    );
    // Do more stuff you normally expect you can do
  });
});
like image 26
user1778856 Avatar answered Nov 19 '22 17:11

user1778856