开玩笑 + 酶,使用 mount(),document.getElementById() 在 _method 调用之后出现的组件上返回 null
Posted
技术标签:
【中文标题】开玩笑 + 酶,使用 mount(),document.getElementById() 在 _method 调用之后出现的组件上返回 null【英文标题】:jest + enzyme, using mount(), document.getElementById() returns null on component which appear after _method call 【发布时间】:2017-09-27 10:39:40 【问题描述】:我的 jest+enzyme mount()
测试遇到了问题。我正在测试一个功能,它切换显示组件。
组件间切换:当state.infoDisplayContent = 'mission'
一个missionControl
组件挂载时,当state.infoDisplayContent = 'profile'
-其他组件介入时:
_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'
);
当我触发这个函数时,一切看起来都很好,这个测试运行良好并且测试成功通过了所需的组件:
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');
);
我得到这个断言:
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)
很明显:
document.getElementById('missionLocationSelect');
return null,但我不明白为什么。正如我提到的,元素通过了测试。
expect(result.find('#missionLocationSelect')).toHaveLength(1);
但无法使用document.getElementById()
捕获。
请帮我解决这个问题并运行测试。
【问题讨论】:
您是否按照这里的 jsdom 酶指南进行操作? airbnb.io/enzyme/docs/guides/jsdom.html#using-enzyme-with-jsdom 【参考方案1】:感谢https://***.com/users/853560/lewis-chung 和 Google 之神找到了解决方案:
通过attachTo
参数将我的组件附加到DOM:
const result = mount(
<App />, attachTo: document.body
);
将我的方法中的错误字符串更改为与元素对象一起使用的字符串
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'
);
【讨论】:
这里真正的问题是你不应该在反应组件中使用document.getElementById
。在表面下,ezyme 的mount
确实安装到了一个真实的 DOM 片段,但它只是没有附加到文档上。这是一个危险信号 - 您应该使用 select
组件的 ref
属性来存储对实际选择节点本身 facebook.github.io/react/docs/refs-and-the-dom.html 或 ReactDOM.findDOMNode
facebook.github.io/react/docs/react-dom.html#finddomnode 的引用。不过,两者都可以通过在此处使用 select 组件的 onChange
事件来避免。
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.
使用ref
为我解决了。有关ref
的更多信息,请参阅React Documentation。
使用 VueJS 它通过使用与 attachtodocument 选项相同的选项vue-test-utils.vuejs.org/api/options.html#attachtodocument 帮助我解决了测试中的问题【参考方案2】:
attachTo: document.body
会产生警告:
警告:render():不鼓励直接将组件渲染到 document.body,因为它的子组件经常被第三方脚本和浏览器扩展所操纵。这可能会导致微妙的和解问题。尝试渲染到为您的应用创建的容器元素中。
所以只需附加到容器元素而不是document.body
,无需将其添加到全局Window对象中
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') );
);
【讨论】:
【参考方案3】:通过 attachTo
参数将你的组件附加到 DOM。
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 );
);
为什么我们需要这个?
mount
仅将组件渲染到 div 元素,而不是将其附加到 DOM 树。
// 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);
,
;
【讨论】:
我尝试对我的测试文件应用相同的更改,但仍然收到相同的错误以上是关于开玩笑 + 酶,使用 mount(),document.getElementById() 在 _method 调用之后出现的组件上返回 null的主要内容,如果未能解决你的问题,请参考以下文章
酶玩笑 window.getSelection() 不起作用
在“if”语句中没有调用模拟函数 - 用玩笑和酶反应应用程序测试?