Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Content of React modal dialogs is not available to Enzyme tests using mount()

I have a React component with a modal dialog (built using reactstrap, but others have reported similar problems with react-bootstrap and other types of modal components). Enzyme cannot find any of the components inside the modal, even though they render fine in the actual app. Minimal example:

import React from 'react'
import { Modal } from 'reactstrap'

export default class MyModal extends React.Component {

    render() {
        return (
            <div className="outside"> Some elements outside of the dialog </div>
            <Modal isOpen={this.props.modalOpen}>
                <div className="inside"> Content of dialog </div>
            </Modal>
         );
    } 
}

I would like to test the contents (in this case using jest) like this

import React from 'react'
import MyModal  from './MyModal'
import { mount } from 'enzyme'

it('renders correctly', () => {
    const wrapper = mount( <MyModal modalOpen/> );

    expect(wrapper).toMatchSnapshot();

    // Passes
    expect(wrapper.find('.outside')).toHaveLength(1);

    // Fails, 0 length
    expect(wrapper.find('.inside')).toHaveLength(1);
});

The test finds the contents outside of the Modal correctly, but does not find anything inside. Looking at the snapshot shows that, indeed, nothing inside the <Modal> is rendered. However it does work if I replace mount with shallow. The problem with that is I need mount to test lifecycle methods like componentDidMount.

Why doesn't mount render the contents of the modal? I thought the whole point was that it rendered the entire tree of child elements.

like image 563
Jonathan Stray Avatar asked Aug 11 '17 21:08

Jonathan Stray


2 Answers

Edit: This is no longer a problem in React 16 + Enzyme 3, because React 16 supports portal components.

In React 15 and before, the problem is that a modal dialog is (in most implementations) a portal component. This means it creates DOM elements that are attached directly to the document root, rather than being children of the parent React component.

The find method of the ReactWrapper created by mount looks through the DOM starting with the element created by the top level component, so it can't find the contents of the modal. But Enzyme's shallow doesn't attach to a DOM, and instead builds its own component tree which contains the modal contents.

To test a portal component, you first need to find the DOM elements that have been attached to the document body. Then you can create a new ReactWrapper around them so that all the usual Enzyme functions work:

import React from 'react'
import MyModal  from './MyModal'
import { mount, ReactWrapper } from 'enzyme'

it('renders correctly', () => {
    const wrapper = mount( <MyModal modalOpen/> );

    expect(wrapper).toMatchSnapshot();

    // Passes
    expect(wrapper.find('.outside')).toHaveLength(1);

    // Construct new wrapper rooted at modal content
    inside_els = document.getElementsByClassName("inside")[0]
    inside_wrapper = new ReactWrapper(inside_els, true)

    // Passes
    expect(inside_wrapper.find('.inside')).toHaveLength(1);
});

Currently, this is an open bug in Enzyme.

Update: It seems that Enzyme also leaves the modal attached to the DOM after the test finishes, so you may end up with multiple dialogs open in a later test. If this is a problem, you can clear the DOM after each test like this:

afterEach(() => {
  var node = global.document.body;
  while (node.firstChild) {
    node.removeChild(node.firstChild);
  }
}); 
like image 143
Jonathan Stray Avatar answered Nov 17 '22 23:11

Jonathan Stray


try mocking the createPortal responsible for showing modals ReactDOM.createPortal = jest.fn(modal => modal);

like image 1
Godspower Omenihu Avatar answered Nov 17 '22 22:11

Godspower Omenihu