Enzyme: Upgrade to React16 and Enzyme3 broke test that use simulate or click event

Created on 29 Sep 2017  ·  49Comments  ·  Source: enzymejs/enzyme

I have updated to React16 and Enzyme3

For some reason, I have many test failing with class looking like this :

    export default class DropdownColorExample extends React.Component {   
      state = {
        dropdownOpen: false,
      }    
      toggle = () => {
        console.log('clicked')
        this.setState({
          dropdownOpen: !this.state.dropdownOpen,
        });
      }    
      render() {
        return (
          <Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
            <DropdownToggle color="primary" caret>
              Dropdown
            </DropdownToggle>
            <DropdownMenu>
              <DropdownItem header>Header</DropdownItem>
              <DropdownItem disabled>Action</DropdownItem>
              <DropdownItem>Another Action</DropdownItem>
              <DropdownItem divider />
              <DropdownItem>Another Action</DropdownItem>
            </DropdownMenu>
          </Dropdown>
        );
      }
    }

Test were both using props directly and simulate() to trigger the state change and the new props of some child components:

Example of test now failing :

    import React from 'react';
    import { mount } from 'enzyme';
    import DropdownColorExample from '../DropdownColorExample';
    const renderComponent = (props = {}) => mount(
      <DropdownColorExample {...props} />
    );    
    describe('<DropdownColorExample />', () => {
      it('should change state when button is clicked', () => {
        const renderedComponent = renderComponent();
        expect(renderedComponent.find('Dropdown').prop('isOpen')).toBe(false);
        renderedComponent.find('button').at(0).prop('onClick')();
        expect(renderedComponent.find('Dropdown').prop('isOpen')).toBe(true);
      });
      it('should change state when button is clicked', () => {
        const renderedComponent = renderComponent();
        expect(renderedComponent.find('Dropdown').props().isOpen).toBe(false);
        renderedComponent.find('button').at(0).simulate('click');
        expect(renderedComponent.find('Dropdown').props().isOpen).toBe(true);
      });
    });

I can see in both case enzyme log my clicks:

console.log app/components/docs/DropdownsDoc/DropdownColorExample.js:11
clicked

But the next test always fail because the child props have not change:

  ● <DropdownColorExample /> › should change state when button is clicked

    expect(received).toBe(expected)

    Expected value to be (using ===):
      true
    Received:
      false

Reproduction

Repo: https://github.com/kopax/enzyme-react16

Broken React16 and Enzyme3

git clone [email protected]:kopax/enzyme-react16.git && cd enzyme-react16 && npm install && npm test

Working React15 and Enzyme2.8

git clone [email protected]:kopax/enzyme-react16.git && cd enzyme-react16 && git checkout v15 && npm install && npm test

Any idea on how I should migrate my tests ?

bug help wanted package 15 package 16

Most helpful comment

@seb0zz that's intentional in v3; you now always have to re-find from the root every time if there's been changes.

All 49 comments

I'm also having problems with Enzyme 3 and simulating events. I'm trying to pass a mocked event object like this:

checkbox.simulate('change', {stopPropagation: () => {}});

But I'm getting a default React event when simulating event:

Expected mock function to have been called with:
      {"stopPropagation": [Function stopPropagation]} as argument 2, but it was called with {"_dispatchInstances": null, "_dispatchListeners": null, "_targetInst": {"_ancestorInfo": {"aTagInScope": null, "buttonTagInScope": null, "current": {"instance": [Circular], "tag": "input"}, "dlItemTagAutoclosing": null, "formTag": null, "listItemTagAutoclosing": null, "nobrTagInScope": null, "pTagInButtonScope": null}, "_contentDebugID": null, "_currentElement": <input checked={false} className="sc-bwzfXH bEFgvg" id="1234" onChange={[Function bound checkboxDidChange]} type="checkbox" />, "_debugID": 9, "_domID": 2, "_flags": 1, "_hostContainerInfo": {"_ancestorInfo": {"aTagInScope": null, "buttonTagInScope": null, "current": {"instance": null, "tag": "div"}, "dlItemTagAutoclosing": null, "formTag": null, "listItemTagAutoclosing": null, "nobrTagInScope": null, "pTagInButtonScope": null}, "_idCounter": 4, "_namespaceURI": "http://www.w3.org/1999/xhtml", "_node": <div><div class="sc-htpNat dsxSlV" data-reactroot=""><input … /><label … /></div></div>, "_ownerDocument": {"_reactListenersID6142566933764189": 0, "location": {"valueOf": [Function valueOf], Symbol(impl): [LocationImpl]}, Symbol(impl): {"_URL": [Object], "_activeNodeIterators": [Array], "_activeNodeIteratorsMax": 10, "_agentOptions": [Object], "_attached": true, "_childNodesList": null, "_childrenList": null, "_contentType": undefined, "_cookieJar": [CookieJar], "_core": [Object], "_currentScript": null, "_customResourceLoader": undefined, "_defaultView": [Window], "_documentElement": [HTMLHtmlElementImpl], "_encoding": "UTF-8", "_eventListeners": [Object], "_global": [Window], "_history": [HistoryImpl], "_htmlToDom": [HtmlToDom], "_ids": [Object], "_implementation": [DOMImplementationImpl], "_lastFocusedElement": null, "_lastModified": "09/29/2017 18:12:26", "_location": [LocationImpl], "_memoizedQueries": [Object], "_nwmatcher": [Object], "_origin": "http://localhost", "_ownerDocument": [Circular], "_parsingMode": "html", "_pool": [Object], "_proxy": undefined, "_queue": [ResourceQueue], "_referrer": "", "_requestManager": [RequestManager], "_strictSSL": true, "_styleSheets": [StyleSheetList], "_version": 11, "nodeType": 9, "readyState": "complete", Symbol(DOM SymbolTree): [SymbolTreeNode], Symbol(wrapper): [Circular]}}, "_tag": "div", "_topLevelWrapper": {"_calledComponentWillUnmount": false, "_compositeType": 0, "_context": {}, "_currentElement": <TopLevelWrapper child={<WrapperComponent … />} />, "_debugID": 0, "_hostContainerInfo": [Circular], "_hostParent": null, "_instance": {"_reactInternalInstance": [Circular], "context": [Object], "props": [Object], "refs": [Object], "rootID": 1, "state": null, "updater": [Object]}, "_mountImage": null, "_mountIndex": 0, "_mountOrder": 1, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, "_pendingReplaceState": false, "_pendingStateQueue": null, "_renderedComponent": {"_calledComponentWillUnmount": false, "_compositeType": 0, "_context": [Object], "_currentElement": <WrapperComponent … />, "_debugID": 3, "_hostContainerInfo": [Circular], "_hostParent": null, "_instance": [WrapperComponent], "_mountImage": null, "_mountIndex": 0, "_mountOrder": 2, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, "_pendingReplaceState": false, "_pendingStateQueue": null, "_renderedComponent": [], "_renderedNodeType": 1, "_rootNodeID": 0, "_topLevelWrapper": [Circular], "_updateBatchNumber": null, "_warnedAboutRefsInRender": false}, "_renderedNodeType": 1, "_rootNodeID": 0, "_topLevelWrapper": null, "_updateBatchNumber":null, "_warnedAboutRefsInRender": false}}, "_hostNode": <input class="sc-bwzfXH bEFgvg" id="1234" type="checkbox" />, "_hostParent": {"_ancestorInfo":{"aTagInScope": null, "buttonTagInScope": null, "current": {"instance": [Circular], "tag": "div"}, "dlItemTagAutoclosing": null, "formTag": null, "listItemTagAutoclosing": null, "nobrTagInScope": null, "pTagInButtonScope": null}, "_contentDebugID": null, "_currentElement": <div className="sc-htpNat dsxSlV"><styled.input checked={false} id="1234" onChange={[Function bound checkboxDidChange]} /><styled.label htmlFor="1234" /></div>, "_debugID": 6, "_domID": 1, "_flags": 1, "_hostContainerInfo": {"_ancestorInfo": {"aTagInScope": null, "buttonTagInScope": null, "current": [Object], "dlItemTagAutoclosing": null, "formTag": null, "listItemTagAutoclosing": null, "nobrTagInScope": null, "pTagInButtonScope": null}, "_idCounter": 4, "_namespaceURI": "http://www.w3.org/1999/xhtml", "_node": <div><div … /></div>, "_ownerDocument": {"_reactListenersID6142566933764189": 0, "location": [Location], Symbol(impl): [DocumentImpl]}, "_tag": "div", "_topLevelWrapper": {"_calledComponentWillUnmount": false, "_compositeType": 0, "_context": [Object], "_currentElement": <TopLevelWrapper … />, "_debugID": 0, "_hostContainerInfo": [Circular], "_hostParent": null, "_instance": [], "_mountImage": null, "_mountIndex": 0, "_mountOrder": 1, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, "_pendingReplaceState": false, "_pendingStateQueue": null, "_renderedComponent": [], "_renderedNodeType": 1, "_rootNodeID": 0, "_topLevelWrapper": null, "_updateBatchNumber": null, "_warnedAboutRefsInRender": false}}, "_hostNode": <div class="sc-htpNat dsxSlV" data-reactroot=""><input class="sc-bwzfXH bEFgvg" id="1234" type="checkbox" /><label class="sc-bdVaJa dcNulu" for="1234" /></div>, "_hostParent": null, "_mountImage": null, "_mountIndex": 0, "_namespaceURI": "http://www.w3.org/1999/xhtml", "_previousStyle": null, "_previousStyleCopy": null, "_renderedChildren": {".0": {"_calledComponentWillUnmount": false, "_compositeType": 0, "_context": [Object], "_currentElement": <styled.input … />, "_debugID": 7, "_hostContainerInfo": [Object], "_hostParent": [Circular], "_instance": [StyledComponent], "_mountImage": null, "_mountIndex": 0, "_mountOrder": 5, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, "_pendingReplaceState": false, "_pendingStateQueue": null, "_renderedComponent": [Circular], "_renderedNodeType": 0, "_rootNodeID": 0, "_topLevelWrapper": null, "_updateBatchNumber": null, "_warnedAboutRefsInRender": false}, ".1": {"_calledComponentWillUnmount": false, "_compositeType": 0, "_context": [Object], "_currentElement": <styled.label … />, "_debugID": 8, "_hostContainerInfo": [Object], "_hostParent": [Circular], "_instance": [StyledComponent], "_mountImage": null, "_mountIndex": 1, "_mountOrder": 6, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, "_pendingReplaceState": false, "_pendingStateQueue": null, "_renderedComponent": [ReactDOMComponent], "_renderedNodeType": 0, "_rootNodeID": 0, "_topLevelWrapper":null, "_updateBatchNumber": null, "_warnedAboutRefsInRender": false}}, "_rootNodeID": 1, "_tag": "div", "_topLevelWrapper": null, "_wrapperState": null}, "_mountImage": null, "_mountIndex": 0, "_namespaceURI": "http://www.w3.org/1999/xhtml", "_previousStyle": null, "_previousStyleCopy": null, "_renderedChildren": null, "_rootNodeID": 2, "_tag": "input", "_topLevelWrapper": null, "_wrapperState": {"controlled": true, "initialChecked": false, "initialValue": undefined, "listeners": [{"remove": [Function remove]}], "onChange": [Function bound _handleChange], "valueTracker": {"getValue": [Function anonymous], "setValue": [Function anonymous], "stopTracking": [Function anonymous]}}}, "bubbles": undefined, "cancelable": undefined, "currentTarget": null, "defaultPrevented": undefined, "dispatchConfig": {"dependencies": ["topBlur", "topChange", "topClick", "topFocus", "topInput", "topKeyDown", "topKeyUp", "topSelectionChange"], "phasedRegistrationNames": {"bubbled": "onChange", "captured": "onChangeCapture"}}, "eventPhase": undefined, "isDefaultPrevented": [Function anonymous], "isPersistent": [Function anonymous], "isPropagationStopped": [Function anonymous], "isTrusted": undefined, "nativeEvent": {"target": <input class="sc-bwzfXH bEFgvg" id="1234" type="checkbox" />, "type": "change"}, "stopPropagation": [Function stopPropagation], "target":<input class="sc-bwzfXH bEFgvg" id="1234" type="checkbox" />, "timeStamp": 1506733947226, "type": "change"}

I'm using Enzyme 3 with React 15.6.1.

I have the same Problem with Enzyme 3.0.0 and React 16.0.0. The event is triggered but the state will not be updated (simulate click).

Just ran into this issue as well. Some of the state is updated but not all of it, here is the code for my test:

import {TabContainer, TabItem} from './tabs';
import {mount, render, shallow} from 'enzyme';

import React from 'react';

const getTabComponent = () => {
    return (
        <TabContainer>
            <TabItem title="one">
                <div>one</div>
            </TabItem>
            <TabItem title="two">
                <div>two</div>
            </TabItem>
        </TabContainer>
    );
}

describe("materials/tabs", () => {
    it("Tabs render minimally without blowing up", () => {
        const wrapper = shallow(getTabComponent());
        expect(wrapper).toHaveLength(1);
    });

    it("Can change tabs", () => {
        const wrapper = mount(getTabComponent());

        const tabs = wrapper.find(".tabs span");
        expect(tabs.length).toEqual(2);

        const activeTab = wrapper.find(".tabs .active");
        expect(activeTab.length).toEqual(1);
        expect(activeTab.at(0).text()).toEqual("one");
        console.log("====BEFORE=====", wrapper.debug());
        tabs.at(1).simulate("click");
        console.log("====AFTER CLICK=====", wrapper.debug());
        wrapper.update();
        console.log("====AFTER UPDATE=====", wrapper.debug());

        const changedTab = wrapper.find(".tabs .active");
        expect(changedTab.length).toEqual(1);
        expect(changedTab.at(0).text()).toEqual("two");

        expect(wrapper).toHaveLength(1);
    });
});

So you can see I log the component before click, after click, and after an update. You will see the property active changes but the className doesn't. But you will also seeing the logs for rendering the tab, it is updating the className.

    console.log static/js/materials/tabs.js:91
      RENDERING TAB one true active
    console.log static/js/materials/tabs.js:91
      RENDERING TAB two false
    console.log static/js/materials/tabs.test.js:34
      ====BEFORE===== <TabContainer>
        <div className="tabs">
          <ul>
            <TabItem title="one" active={true} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="active">
                  one
                </span>
              </li>
            </TabItem>
            <TabItem title="two" active={false} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="">
                  two
                </span>
              </li>
            </TabItem>
          </ul>
          <div className="TabContent">
            <div>
              one
            </div>
          </div>
        </div>
      </TabContainer>
    console.log static/js/materials/tabs.js:91
      RENDERING TAB one false
    console.log static/js/materials/tabs.js:91
      RENDERING TAB two true active
    console.log static/js/materials/tabs.test.js:36
      ====AFTER CLICK===== <TabContainer>
        <div className="tabs">
          <ul>
            <TabItem title="one" active={false} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="active">
                  one
                </span>
              </li>
            </TabItem>
            <TabItem title="two" active={true} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="">
                  two
                </span>
              </li>
            </TabItem>
          </ul>
          <div className="TabContent">
            <div>
              two
            </div>
          </div>
        </div>
      </TabContainer>
    console.log static/js/materials/tabs.test.js:38
      ====AFTER UPDATE===== <TabContainer>
        <div className="tabs">
          <ul>
            <TabItem title="one" active={false} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="active">
                  one
                </span>
              </li>
            </TabItem>
            <TabItem title="two" active={true} onClick={[Function]}>
              <li>
                <span onClick={[Function]} className="">
                  two
                </span>
              </li>
            </TabItem>
          </ul>
          <div className="TabContent">
            <div>
              two
            </div>
          </div>
        </div>
      </TabContainer>

@lelandrichardson @ljharb Any idea what could cause this? Upgrading enzyme and react broke majority of my tests because of updates not being reflected.

We are observing this as well.

FWIW, the example in the docs (http://airbnb.io/enzyme/docs/api/ReactWrapper/simulate.html) fails as well

What about React 15 and enzyme 3?

In other words, your tests on React 16 never worked on enzyme 2, so we'd need you to compare between react 15 and 16 on enzyme 3, to know whether the behavior change was caused by enzyme 3, or by the react 16 adapter.

Yeah, it breaks on react 15 as well, here is the change I made to break it:

diff --git a/package.json b/package.json
index 966c513..7692c9e 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,8 @@
     "draftjs-utils": "^0.8.4",
     "emoji-flags": "^1.2.0",
     "env-cmd": "^5.0.0",
+    "enzyme-adapter-react-15": "^1.0.0",
     "eslint": "^4.0.0",
     "eslint-loader": "^1.8.0",
     "eslint-plugin-react": "^7.1.0",
@@ -156,8 +158,8 @@
     "concurrently": "^3.4.0",
     "deep-diff": "^0.3.4",
     "deep-freeze": "latest",
-    "enzyme": "^2.8.2",
-    "enzyme-to-json": "^2.0.0",
+    "enzyme": "^3.0.0",
+    "enzyme-to-json": "^3.0.1",
     "eslint-plugin-import": "^2.2.0",
     "express": "^4.15.3",
     "happypack": "^3.0.3",
diff --git a/static/js/tests/setup.js b/static/js/tests/setup.js
index bfe1030..8321b99 100644
--- a/static/js/tests/setup.js
+++ b/static/js/tests/setup.js
@@ -1,3 +1,7 @@
 import "babel-polyfill";

+import Adapter from 'enzyme-adapter-react-15';
+import { configure } from 'enzyme';
 import fetch from 'isomorphic-fetch';
+
+configure({ adapter: new Adapter() });

Which is:

  • Upgrade enzyme/enzyme-to-json
  • Add the adapter and configure it

and now all my tests fail

I was having the same issue, then I downgraded enzyme to v 2.9.0 and it started working again :). I'll have to read the documentation of v3 to make the changes..

@kopax sorry if you misunderstood it. I didnt solve it

Also really looking for some help here. simulate('mouseenter') stopped working once we upgraded enzyme and added the r16 adapter. We've managed to get everything else working.
We're using enzyme 3.1.0 with enzyme-adapter-react-16 1.0.1

@HipsterZipster does it work with enzyme 3 and the react 15 adapter?

@ljharb do you mean converting the actual react / react-dom dependencies to use r15 with the r15 enzyme adapter or just trying the r15 adapter with r16 dependencies?

I did the conversion of using enzyme3 with react15 and it broke, you can see the proof in my comment here:

https://github.com/airbnb/enzyme/issues/1201#issuecomment-333671256

Also, as the commenter above noted the examples in the documentation don't work with react15 + enzyme3:

https://github.com/airbnb/enzyme/issues/1201#issuecomment-333659921

@HipsterZipster i mean getting your codebase passing tests on enzyme 3 with react 15, prior to upgrading to react 16 - upgrading to react 16 and enzyme 3 at the same time is reckless :-)

@sontek thanks

Anyone looking into this? I don't know enough about enzyme to contribute but this is the only dependency preventing us from upgrading.

If there is anymore things I can do to help reproduce and move it along, I'm willing to do so.

@lelandrichardson, any thoughts?

I am having exactly the same problem, our core team decided to update React from 15.3.2 -> 15.6.2 and we got errors running enzyme 3.1.0.

The only tests got broken (broken === doesn't do anything) were related with simulate input and select changes, the rest of my simulate are working fine. I tried multiple combinations without luck. Example below

package.json

"dependencies": {
  "react": "15.6.2",
  "react-dom": "15.6.2",
  "react-redux": "^5.0.6",
  "react-router": "^4.1.2",
  "redux": "^3.7.2",
  "redux-form": "^6.7.0",
  "sinon": "^2.2.0",
...
},
"devDependencies": {
  "enzyme": "^3.1.0",
  "enzyme-adapter-react-15": "^1.0.2",
  "jsdom": "^11.0.0",
  "mocha": "3.2.0",
....
}
it('should map state to timeWindows prop', () => {
  const store = createFakeStore(initialState);
  const wrapper = mount(
    <Provider store={store}>
      <BookingFormContainer {...props}/>
    </Provider>
  );

  const container = wrapper.find('BookingForm');
  const datesDropwDown = wrapper.find('select[name="date"]');

  const dateToSelect = initialState.booking.dates[1];
  datesDropwDown.simulate('change', { target: { value: dateToSelect.date } });

  assert.deepEqual(container.props().timeWindows, [
    { value: '0', label: expectedGeneratedFormatedTime(dateToSelect.timeWindows[0]) },
    { value: '1', label: expectedGeneratedFormatedTime(dateToSelect.timeWindows[1]) }
  ]);
});

I believe React 15.6 changed how the "change" event works; it no longer fires when the value hasn't actually changed. I'm not sure if that's your issue or not.

@ljharb currently the value pass from [] to dateToSelect.date which is not empty. However, the value on the asset is empty so it doesn't update when I run simulate click.

Perhaps, it's related in how should I target the events, but I am using redux-form for that which in theory update the value before the upgrade.

I have the same issue, when I updated
"react": "^16.0.0", "react-dom": "^16.0.0", "chai": "^4.1.1", "chai-enzyme": "^1.0.0-beta.0", "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.2", "mocha": "^4.0.1", "react-addons-test-utils": "^15.6.2", "react-test-renderer": "^16.0.0", "sinon": "^4.0.1",

My test failed with error AssertionError: expected false to be true, before updating it works,

 it('should set isUserInfoShown to true and userInfoPositionElement from input event when showUserInfo is called', () => {
        let currentTarget = {
            type: 'button',
        }
        let spy = sinon.spy()
        let event = {
            preventDefault: spy,
            currentTarget: currentTarget,
        }
        account.find(AccountButton).props().onClick(event)
        spy.calledOnce.should.be.true
        account.find(AccountButton).props().isPopoverShown.should.be.true

        account.find(AccountButton).props().positionElement.should.be.eql(currentTarget)
    })

When I change two lines, test passed
account.find(AccountButton).simulate('click', event)
expect(account.find(AccountButton).props().isPopoverShown).to.be.true

Now we should always use expect instead should.be.true? If so, should may be better use some codemode
why does simulate work?

The issue with was solved after I use account.update()

I am having the same issue with 3.1 and react 16 adapter.

            policySelect.simulate('change', { target: { value: 123 } });
            expect(policySelect.prop('value')).to.equal(123);

This no longer works, but did before.

FYI, if you "refind" the element, it does work. For example

let policySelect = wrapper.find('select').at(0);
/// other code
policySelect.simulate('change', { target: { value: 123 } });
// you have to find it again
policySelect = wrapper.find('select').at(0);
expect(policySelect.prop('value')).to.equal(123);

@stevenmusumeche do you use mount or shallow ?, what error did you get ?

In this case, mount.

@stevenmusumeche simulate update component automatically, so you should find it again

Thank you! I didn't understand that breaking change. Simple fix for me.

I've been doing some experiments with upgrading our dependencies for React and Enzyme, and with R16 and Enzyme 3, these simulate tests have been failing. Strangely, it seems simulating _twice_ lets it show up _once_.

e.g.

const component = mount(<TestComponent />);
const spy = jest.spyOn(component.instance(), 'handleClick');
component.update();
component.find('button').simulate('click'); // <-- fails if it is only called once
component.update(); // <-- forcing a second update doesn't do anything
component.find('button').simulate('click'); // <-- succeeds if we simulate a second time
expect(spy).toHaveBeenCalled();

Is there perhaps some kind of async action going on?

Edit: To test further, I went back to the Master branch which is still on React 15.6 and upgraded just Enzyme from 2.9.1 to 3.1.0 (adding the React 15 adapter). This above behaviour happens as well.

Anyone having issues with React 16 and enzyme 3: please first ensure that your tests pass on React 15 and enzyme 3, so that we can narrow down the failure to "upgrading to react 16 and switching to the react 16 adapter".

I have the same issue with React 15.x and Enzyme 3: the click event has been triggered, but the prop(className) has not been updated correspondingly, however, when I use console.log(wrapper.html()) to print the node,the className has been updated already :

image

@ljharb My example was a pure migration problem the test passed on 15.3.2 but it doesn't on 15.6.2, I will try to figure out the problem check the library internally, but no promises :)

@EduardoAC thanks, if you could file a new issue for your concern, that might be a difference between the 15.4 and the 15 adapters (this issue is primarily about react 16)

@ljharb Is it primarily about react 16 if the same error happens on both? My tests fail the same as the issue on both React15 and React16, the only difference being enzyme3

@sontek i guess it'd depend on whether it was in the adapter or in enzyme itself; certainly if it's across adapters that suggests it's related to enzyme itself.

@ljharb Yeah, well I've seen at least @mikehdt [1], @EduardoAC [2], Me [3], and @figalex [4] report that we are experiencing this issue on React 15, so it does seem to be more widespread.

Also, I switched the original reproduction code from the creator of the ticket to react15 and their tests still fail, but with enzyme 2 they pass.

https://github.com/sontek/enzyme-react16/tree/react15

[1] https://github.com/airbnb/enzyme/issues/1201#issuecomment-340310335
[2] https://github.com/airbnb/enzyme/issues/1201#issuecomment-338570970
[3] https://github.com/airbnb/enzyme/issues/1201#issuecomment-333671256
[4] https://github.com/airbnb/enzyme/issues/1201#issuecomment-333271981

with shallow component the simulate works as expected and it update() not necessary with simulate, but with mount it deals with real dom and jsdom so the event system kicks out the real event with real nodes, so current solution with mount for me

wrapper.find('component').props().onChange(eventObject('foo')));  
wrraper.update()

With shallow you can use simulate as expected

I'm not sure if this is the same issue, but I thought it would help with investigation.

The below test case fails for me with:

import { shallow } from 'enzyme';
import React from 'react';

class Test extends React.Component {
    render () {
        return null;
    }
}

class Wrapper extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            somethingHappened: false,
            componentDidUpdateWasCalled: false,
        };

        this.someHandler = this.someHandler.bind(this);
    }

    componentDidUpdate() {
        !this.state.componentDidUpdateWasCalled && this.setState({
            componentDidUpdateWasCalled: true,
        });
    }

    someHandler() {
        this.setState({ somethingHappened: true });
    }

    render() {
        return <Test onSomething={this.someHandler} />;
    }
}

describe('enzyme test', () => {
  it('calls componentDidUpdate', () => {
    const wrapper = shallow(<Wrapper />);

    wrapper.find(Test).prop('onSomething')();
    wrapper.update(); // makes no difference - not required for react 15 passing test

    expect(wrapper.state('somethingHappened')).toBe(true); // passes
    expect(wrapper.state('componentDidUpdateWasCalled')).toBe(true); // fails
  });
});

It passes with:

For me the following code does not work:

const wrapper = mount(<Foo />);
const button = wrapper.find('button');
const span = wrapper.find('span');
button.simulate('click');
expect(span).toHaveText('clicked');

but this does:

const wrapper = mount(<Foo />);
const button = wrapper.find('button');
button.simulate('click');
expect(wrapper.find('span')).toHaveText('clicked');

Placing a wrapper.update() somewhere in between has no effect. So I am only able to fix it by calling .find() again on the root element after triggering the event.

@seb0zz that's intentional in v3; you now always have to re-find from the root every time if there's been changes.

@ljharb okay why is that? Don't think this is really intuitive 😞

It's in the migration guide; it makes found subwrappers immutable, which simplifies the code and makes the code easier to reason about: https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#element-referential-identity-is-no-longer-preserved

@ljharb thanks, get the point. Nevertheless, the side effect

that enzyme no longer has access to the actual object references

is pretty confusing from my point of view. Wrote like a few 100 unit tests with enzyme and found having the reference quit comfortable 😉

Any suggestions on what to do with something like this? I've tried every suggestion I can find online and nothing has worked.

const component = mount(
  <CheckboxGroup title={"CheckboxGroup test"}>
    <Checkbox name={"1"} />
    <Checkbox name={"2"} />
    <Checkbox name={"3"} />
    <Checkbox name={"4"} />
  </CheckboxGroup>
);

component.childAt(1).simulate("click");

expect(component.state().totalChecked).toBe(1);

Update: I had to change the path to find the correct child to click (component.childAt(0).childAt(1).simulate("click");), and then call .update() post click (component.update();). This is resolved.

Is there a suggested way of writing tests with this update that doesn't require re-writing the code to find elements? In my case, an input element is having its prop changed after a change event fires. To make it pass, I need to find the element before and after. I'm not entirely sure yet how this is desirable behavior, but I am totally open to seeing how it is. Just wanting to learn the best pattern for it now. Thanks!

Is there a suggested way of writing tests with this update that doesn't require re-writing the code to find elements?

@cavaloni I'm pretty sure the answer is no. You need to refind the element - see https://github.com/airbnb/enzyme/blob/0fe0d730ef4f27e99144e5be7ec292f3ce014003/docs/guides/migration-from-2-to-3.md#for-mount-updates-are-sometimes-required-when-they-werent-before

...This has many advantages, but one of the side effects is that now the intermediate representation does not receive automatic updates.

From all the chatter above, it seems like a fair number of comments are just confusion around the 3 breaking changes? @ljharb to be honest I found that migration guide really hard to follow - it reads more on the implementation detail level - even re-reading it now it's hard for me to pull the message "you now always have to re-find from the root every time if there's been changes" from https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#element-referential-identity-is-no-longer-preserved which focuses on a discussion around removing duplicates

Pulling https://github.com/sontek/enzyme-react16/tree/react15 down and switching to the react15 branch, it did seem like 3.0.0 failed and 2.9.1 succeeded - note that you need to be on the react15 branch since 2.9.1 does not work with v16 (you get TypeError: Cannot read property 'getPublicInstance' of undefined)

I decided to give bisecting (left some comments in the channel) a try following https://web.archive.org/web/20150830120011/https://medium.com/@rasjani/debugging-3rd-party-npm-module-regression-d10530f2b67 but for now I've give up - the steps are tricky (switching node versions, lerna run build, etc) - might circle back around but this bug isn't biting me (yet)

@jcrben PRs to improve the migration guide are welcome! I’m quite sure it’s not perfect :-)

The same commit won’t work with both enzyme 2 and 3; the important part is to get things passing on enzyme 3 before changing your react version.

I believe this is answered; PRs to the migration guide/docs, and new issues if needed, are both welcome and encouraged.

Was this page helpful?
0 / 5 - 0 ratings