Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jest + enzyme, using mount(), document.getElementById() returns null on component which appear after _method call

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' a missionControl component is mounted, when state.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.

like image 740
Dmytro Zhytomyrsky Avatar asked Apr 29 '17 11:04

Dmytro Zhytomyrsky


People also ask

What does Enzyme do in Jest?

Jest is a unit test framework designed by Facebook to test react applications. Jest allows to write unit tests using the Snapshot feature. Enzyme allows to write unit tests using regular assertions. Using Enzyme with Jest makes writing tests for React applications a lot easier.

Which is better Enzyme or Jest?

Shallow rendering is one way that Enzyme keeps tests simpler than Jest. When you shallow-render a component with Enzyme, you render only that component. Enzyme doesn't render any of the children of that component. This is a useful restriction that ensures that you aren't testing too much in one test.

What is the difference between shallow and mount in Enzyme?

Always begin with shallow. If componentDidMount or componentDidUpdate should be tested, use mount. If you want to test component lifecycle and children behavior, use mount. If you want to test children rendering with less overhead than mount and you are not interested in lifecycle methods, use render.


2 Answers

Found the solution thanks to https://stackoverflow.com/users/853560/lewis-chung and gods of Google:

  1. Attached my component to DOM via attachTo param:

    const result = mount(     <App />, { attachTo: document.body } ); 
  2. 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'             });         }     } 
like image 138
Dmytro Zhytomyrsky Avatar answered Sep 29 '22 03:09

Dmytro Zhytomyrsky


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') });
});
like image 28
Tina Chen Avatar answered Sep 29 '22 03:09

Tina Chen