I have a React component with a modal dialog (built using reactstrap
, but others have reported similar problems with react-bootstrap
and other type
try mocking the createPortal responsible for showing modals ReactDOM.createPortal = jest.fn(modal => modal);
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);
}
});
In case you are using an older version of Enzyme, you can pass the container element to mount
where you want your Modal
to be rendered, like this:
import React from 'react'
import MyModal from './MyModal'
import { mount } from 'enzyme'
describe(() => {
let wrapper;
beforeEach(() => {
const container = document.createElement("div");
document.body.appendChild(container);
wrapper = mount( <MyModal modalOpen/> , {attachTo: container});
});
it('renders correctly', () => {
expect(wrapper).toMatchSnapshot();
// Passes
expect(wrapper.find('.outside')).toHaveLength(1);
// Passes now
expect(wrapper.find('.inside')).toHaveLength(1);
});
})