How to test react-modal


Let’s say we have <ModalContainer> that renders react-modal:

// ModalContainer.js
import React, { Component } from 'react';
import Modal from 'react-modal';

class ModalContainer extends Component {
  state = {
    isModalOpen: false,
  };

  toggleModal = () => {
    this.setState({
      isModalOpen: !this.state.isModalOpen,
    });
  };

  render() {
    return (
      <div>
        <button onClick={this.toggleModal}>Open Modal</button>
        <Modal
          isOpen={this.state.isModalOpen}
          onRequestClose={this.toggleModal}
        >
          {this.props.children}
        </Modal>
      </div>
    );
  }
}

export default ModalContainer;

How would we write a test for the modal component?

Enzyme find

You can use enzyme find to verify that react-modal renders:

// ModalContainer.test.js
import { shallow } from 'enzyme';
import Modal from 'react-modal';
import ModalContainer from './ModalContainer';
import React from 'react';

it('renders react-modal', () => {
  const wrapper = shallow(<ModalContainer />);
  expect(wrapper.find(Modal)).toHaveLength(1);
});

Or test that react-modal prop isOpen changes:

it('opens modal when button is clicked', () => {
  const wrapper = shallow(<ModalContainer />);
  expect(wrapper.find(Modal).prop('isOpen')).toBe(false);

  wrapper.find('button').simulate('click');
  expect(wrapper.find(Modal).prop('isOpen')).toBe(true);
});

Or check that react-modal children matches what you expect:

it('renders childen when modal is open', () => {
  const wrapper = shallow(<ModalContainer>modal content</ModalContainer>);
  expect(wrapper.find(Modal).prop('children')).toBe('modal content');
  // expect(wrapper.find(Modal).prop('children')).toMatchSnapshot();
});

However, these tests only test implementation details, which has low value.

So how can we write more valuable tests that confirm the modal content is rendered when it’s opened?

We could try something like this:

it('renders content when modal is open', () => {
  const wrapper = shallow(<ModalContainer>modal content</ModalContainer>);
  wrapper.find('button').simulate('click');
  expect(wrapper.find(Modal).text()).toBe('modal content');
});

But it ends up failing:

✕ renders content when modal is open

expect(received).toBe(expected)

Expected value to be (using ===):
  "modal content"
Received:
  "<Modal />"

  27 | it('renders content when modal is open', () => {
  28 |   const wrapper = shallow(<ModalContainer>modal content</ModalContainer>);
  29 |   wrapper.find('button').simulate('click');
> 30 |   expect(wrapper.find(Modal).text()).toBe('modal content');
  31 | });

To fix it, we can do one of the following:

mount

The advantage of enzyme mount is it renders the element to an actual DOM (browser or jsdom):

import { mount } from 'enzyme';

// ...
it('renders content when modal is open', () => {
  // mount renders to the dom (real or mocked)
  const wrapper = mount(<ModalContainer>modal content</ModalContainer>);
  wrapper.find('button').simulate('click');

  // element text
  expect(wrapper.find(Modal).text()).toBe('modal content');
});

node.innerText

instance

To get react-modal node (which is an actual DOM node), you need to get the modal instance:

it('renders content when modal is open', () => {
  // mount renders to the dom (real or mocked)
  const wrapper = mount(<ModalContainer>modal content</ModalContainer>);
  wrapper.find('button').simulate('click');

  // instance node
  const instance = wrapper.find(Modal).instance();
  expect(instance.node.innerText).toBe('modal content');
});

Then you can call innerText on the node to get the rendered text.

findDOMNode

Since react-modal has a portal property, you can use findDOMNode to get the node:

import { findDOMNode } from 'react-dom';

// ...
it('renders content when modal is open', () => {
  // mount renders to the dom (real or mocked)
  const wrapper = mount(<ModalContainer>modal content</ModalContainer>);
  wrapper.find('button').simulate('click');

  // findDOMNode
  const portalNode = findDOMNode(instance.portal);
  expect(portalNode.innerText).toBe('modal content');
});

Demo

The code and tests can be found on CodeSandbox:



Please support this site and join our Discord!