jest + enzyme,使用mount(),document.getElementById()在_method调用之后出现的组件上返回null

Dmy*_*sky 18 testing mount reactjs jestjs 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'
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

当我触发这个函数时,一切看起来都很好,这个测试运行良好,测试成功传递了所需的组件:

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');
});
Run Code Online (Sandbox Code Playgroud)

我得到这个断言:

    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)
Run Code Online (Sandbox Code Playgroud)

很明显:

document.getElementById('missionLocationSelect');
Run Code Online (Sandbox Code Playgroud)

返回null,但我无法理解.正如我所提到的,元素通过测试.

expect(result.find('#missionLocationSelect')).toHaveLength(1);
Run Code Online (Sandbox Code Playgroud)

但它无法捕获document.getElementById().

请帮我解决这个问题并运行测试.

Dmy*_*sky 22

通过/sf/users/59749231/和Google的众神找到了解决方案:

  1. 通过attachToparam将我的组件附加到DOM :

    const result = mount( <App />, { attachTo: document.body } );

  2. 将我的方法中的错误字符串更改为与元素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'
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这里真正的问题是你不应该在react组件中使用`document.getElementById`.在表面下,酶的"mount"确实安装到一个真正的DOM片段,但它没有附加到文档中.这是一个红色标志 - 您应该使用`select`组件的`ref` prop来存储对实际选择节点本身的引用https://facebook.github.io/react/docs/refs-and-the -dom.html,或`ReactDOM.findDOMNode`https://facebook.github.io/react/docs/react-dom.html#finddomnode.这两个都可以通过使用select组件的`onChange`事件来避免. (6认同)
  • `警告:render():不鼓励将组件直接渲染到document.body中,因为它的子节点经常被第三方脚本和浏览器扩展所操纵.这可能会导致细微的和解问题.尝试渲染为为您的应用程序创建的容器元素 (4认同)
  • 使用`ref`solved for me.有关`ref`的更多信息,请参阅[React Documentation](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs). (3认同)

Tin*_*hen 16

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') });
});
Run Code Online (Sandbox Code Playgroud)


vin*_*avi 8

通过attachToparam 将组件附加到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 });
});
Run Code Online (Sandbox Code Playgroud)

为什么我们需要这个?

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);
      },
    };
  }
Run Code Online (Sandbox Code Playgroud)