jest + enzyme, using mount(), document.getElementById() returns null on component which appear after _method call
Solution 1
Found the solution thanks to https://stackoverflow.com/users/853560/lewis-chung and gods of Google:
-
Attached my component to DOM via
attachTo
param:const result = mount( <App />, { attachTo: document.body } );
Changed buggy string in my method to string which works with element Object
agentToMod.location = locationSelect.options[locationSelect.selectedIndex].text;` :
_modifyAgentStatus () {
const { currentAgentProfile, agentsDatabase } = this.state;
const agentToMod = currentAgentProfile;
if (agentToMod.status === 'Free') {
this.setState({
infoDisplayContent: 'mission'
});
agentToMod.status = 'Waiting';
} else if (agentToMod.status === 'Waiting') {
const locationSelect = document.getElementById('missionLocationSelect');
agentToMod.location = agentToMod.location = locationSelect.options[locationSelect.selectedIndex].text;
agentToMod.status = 'On Mission';
this.setState({
infoDisplayContent: 'profile'
});
}
}
Solution 2
attachTo: document.body
will generate a warning:
Warning: render(): Rendering components directly into document.body is discouraged, since its children are often manipulated by third-party scripts and browser extensions. This may lead to subtle reconciliation issues. Try rendering into a container element created for your app.
So just attach to a container element instead of document.body
, and no need to add it to the global Window object
before(() => {
// Avoid `attachTo: document.body` Warning
const div = document.createElement('div');
div.setAttribute('id', 'container');
document.body.appendChild(div);
});
after(() => {
const div = document.getElementById('container');
if (div) {
document.body.removeChild(div);
}
});
it('should display all contents', () => {
const wrapper = mount(<YourComponent/>,{ attachTo: document.getElementById('container') });
});
Solution 3
Attached your component to DOM via attachTo
param.
import { mount} from 'enzyme';
// Avoid Warning: render(): Rendering components directly into document.body is discouraged.
beforeAll(() => {
const div = document.createElement('div');
window.domNode = div;
document.body.appendChild(div);
})
test("Test component with mount + document query selector",()=>{
const wrapper = mount(<YourComponent/>,{ attachTo: window.domNode });
});
why we need this?
mount
only render component to div element not attached it to DOM tree.
// Enzyme code of mount renderer.
createMountRenderer(options) {
assertDomAvailable('mount');
const domNode = options.attachTo || global.document.createElement('div');
let instance = null;
return {
render(el, context, callback) {
if (instance === null) {
const ReactWrapperComponent = createMountWrapper(el, options);
const wrappedEl = React.createElement(ReactWrapperComponent, {
Component: el.type,
props: el.props,
context,
});
instance = ReactDOM.render(wrappedEl, domNode);
if (typeof callback === 'function') {
callback();
}
} else {
instance.setChildProps(el.props, context, callback);
}
},
unmount() {
ReactDOM.unmountComponentAtNode(domNode);
instance = null;
},
getNode() {
return instance ? instanceToTree(instance._reactInternalInstance).rendered : null;
},
simulateEvent(node, event, mock) {
const mappedEvent = mapNativeEventNames(event);
const eventFn = TestUtils.Simulate[mappedEvent];
if (!eventFn) {
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
}
// eslint-disable-next-line react/no-find-dom-node
eventFn(ReactDOM.findDOMNode(node.instance), mock);
},
batchedUpdates(fn) {
return ReactDOM.unstable_batchedUpdates(fn);
},
};
}
Dmytro Zhytomyrsky
Updated on February 02, 2020Comments
-
Dmytro Zhytomyrsky over 4 years
I faced a problem with my jest+enzyme
mount()
testing. I am testing a function, which switches displaying components.Switch between components: when
state.infoDisplayContent = 'mission'
amissionControl
component is mounted, whenstate.infoDisplayContent = 'profile'
- other component steps in:_modifyAgentStatus () { const { currentAgentProfile, agentsDatabase } = this.state; const agentToMod = currentAgentProfile; if (agentToMod.status === 'Free') { this.setState({ infoDisplayContent: 'mission' }); agentToMod.status = 'Waiting'; } else if (agentToMod.status === 'Waiting') { const locationSelect = document.getElementById('missionLocationSelect'); agentToMod.location = locationSelect[locationSelect.selectedIndex].innerText; agentToMod.status = 'On Mission'; this.setState({ infoDisplayContent: 'profile' }); } }
When I trigger this function everything looks Ok, this test runs well and test successfully pass with required component:
import React from 'react'; import { mount } from 'enzyme'; import App from '../containers/App'; const result = mount( <App /> ) test('change mission controls', () => { expect(result.state().currentAgentProfile.status).toBe('Free'); result.find('#statusController').simulate('click'); expect(result.find('#missionControls')).toHaveLength(1); expect(result.find('#missionLocationSelect')).toHaveLength(1); expect(result.state().currentAgentProfile.status).toBe('Waiting'); }); But when I simulate onClick two times: test('change mission controls', () => { expect(result.state().currentAgentProfile.status).toBe('Free'); result.find('#statusController').simulate('click'); expect(result.find('#missionControls')).toHaveLength(1); expect(result.find('#missionLocationSelect')).toHaveLength(1); expect(result.state().currentAgentProfile.status).toBe('Waiting'); result.find('#statusController').simulate('click'); expect(result.state().currentAgentProfile.status).toBe('On Mission'); });
I get this assert:
TypeError: Cannot read property 'selectedIndex' of null at App._modifyAgentStatus (development/containers/App/index.js:251:68) at Object.invokeGuardedCallback [as invokeGuardedCallbackWithCatch] (node_modules/react-dom/lib/ReactErrorUtils.js:26:5) at executeDispatch (node_modules/react-dom/lib/EventPluginUtils.js:83:21) at Object.executeDispatchesInOrder (node_modules/react-dom/lib/EventPluginUtils.js:108:5) at executeDispatchesAndRelease (node_modules/react-dom/lib/EventPluginHub.js:43:22) at executeDispatchesAndReleaseSimulated (node_modules/react-dom/lib/EventPluginHub.js:51:10) at forEachAccumulated (node_modules/react-dom/lib/forEachAccumulated.js:26:8) at Object.processEventQueue (node_modules/react-dom/lib/EventPluginHub.js:255:7) at node_modules/react-dom/lib/ReactTestUtils.js:350:22 at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-dom/lib/Transaction.js:140:20) at Object.batchedUpdates (node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js:62:26) at Object.batchedUpdates (node_modules/react-dom/lib/ReactUpdates.js:97:27) at node_modules/react-dom/lib/ReactTestUtils.js:348:18 at ReactWrapper.<anonymous> (node_modules/enzyme/build/ReactWrapper.js:776:11) at ReactWrapper.single (node_modules/enzyme/build/ReactWrapper.js:1421:25) at ReactWrapper.simulate (node_modules/enzyme/build/ReactWrapper.js:769:14) at Object.<anonymous> (development/tests/AgentProfile.test.js:26:38) at process._tickCallback (internal/process/next_tick.js:109:7)
It is obvious that:
document.getElementById('missionLocationSelect');
return null, but I can not get why. Element passes tests, as I mention.
expect(result.find('#missionLocationSelect')).toHaveLength(1);
But it could not be captured with
document.getElementById()
.Please, help me to fix this problem and run tests.
-
alechill almost 7 yearsThe real issue here is that you should never use
document.getElementById
within a react component. Under the surface ezyme'smount
does mount to a real DOM fragment but it just isn't attached to the document. This is a red flag - you should instead use theref
prop of theselect
component to store a reference to the actual select node itself facebook.github.io/react/docs/refs-and-the-dom.html, orReactDOM.findDOMNode
facebook.github.io/react/docs/react-dom.html#finddomnode . Both can be avoided by using theonChange
event of the select component here though. -
Neurotransmitter over 6 years
Warning: render(): Rendering components directly into document.body is discouraged, since its children are often manipulated by third-party scripts and browser extensions. This may lead to subtle reconciliation issues. Try rendering into a container element created for your app.
-
a.barbieri about 6 years
-
darul75 about 5 yearsusing VueJS it helped me to sort out what was wrong in a test by using same kind of option with attachtodocument option vue-test-utils.vuejs.org/api/options.html#attachtodocument
-
user 9191 over 4 yearsI tried to apply the same changes to my test file and still getting the same error