web-dev-qa-db-fra.com

jest + enzyme, en utilisant mount (), document.getElementById () retourne null sur le composant qui apparaît après l'appel de _method

J'ai rencontré un problème avec mes tests jest + enzyme mount(). Je teste une fonction qui bascule l'affichage des composants.

Basculer entre les composants: lorsque state.infoDisplayContent = 'mission' Un composant missionControl est monté, lorsque state.infoDisplayContent = 'profile' - d'autres composants interviennent dans:

_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'
        });
    }
}

Lorsque je déclenche cette fonction, tout semble OK, ce test fonctionne bien et le test réussit avec le composant requis:

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');
});

Je reçois cette affirmation:

    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)

Il va de soi que:

document.getElementById('missionLocationSelect');

renvoie null, mais je ne comprends pas pourquoi. L'élément passe les tests, comme je l'ai mentionné.

expect(result.find('#missionLocationSelect')).toHaveLength(1);

Mais il n'a pas pu être capturé avec document.getElementById().

S'il vous plaît, aidez-moi à résoudre ce problème et à exécuter des tests.

16
Dmytro Zhytomyrsky

Trouvé la solution grâce à https://stackoverflow.com/users/853560/lewis-chung et aux dieux de Google:

  1. Attaché mon composant au DOM via attachTo param:

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

  2. Chaîne buggy modifiée dans ma méthode en chaîne qui fonctionne avec l'élément 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'
        });
    }
}
21

Attaché votre composant au 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 });
});

pourquoi nous en avons besoin?

mount rend uniquement le composant à l'élément div non attaché à l'arborescence 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);
      },
    };
  }
7
vinay mavi

attachTo: document.body générera un avertissement:

Avertissement: render (): le rendu des composants directement dans document.body est déconseillé, car ses enfants sont souvent manipulés par des scripts tiers et des extensions de navigateur. Cela peut entraîner de subtils problèmes de réconciliation. Essayez d'effectuer le rendu dans un élément de conteneur créé pour votre application.

Il suffit donc de l'attacher à un élément conteneur au lieu de document.body, et pas besoin de l'ajouter à l'objet Window global

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') });
});
1
Tina Chen