Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing Hooks with shallow: Invariant Violation #1938

Closed
2 of 13 tasks
TdyP opened this issue Dec 11, 2018 · 79 comments
Closed
2 of 13 tasks

Testing Hooks with shallow: Invariant Violation #1938

TdyP opened this issue Dec 11, 2018 · 79 comments

Comments

@TdyP
Copy link

TdyP commented Dec 11, 2018

Current behavior

When testing component which contains newly released React Hooks using shallow, it crashes:
Invariant Violation: Hooks can only be called inside the body of a function component.

Everything works fine at run time or when testing with render:

My test component:

import * as React from 'react';

function Test() {
    const [myState, setMyState] = React.useState('Initial state');

    const changeState = () => setMyState('State updated');

    return (
        <div>
            {myState}
            <button onClick={changeState}>Change</button>
        </div>
    );
}

export default Test;

My test file:

import { shallow } from 'enzyme';
import * as React from 'react';
import Test from './Test';

it('renders without crashing', () => {
    const comp = shallow(<Test />);

    expect(comp.find('Test')).toMatchSnapshot();
});

Error stack trace:

Invariant Violation: Hooks can only be called inside the body of a function component.

    at invariant (node_modules/react/cjs/react.development.js:125:15)
    at resolveDispatcher (node_modules/react/cjs/react.development.js:1450:28)
    at Object.useState (node_modules/react/cjs/react.development.js:1473:20)
    at Test (src/Test.tsx:4:11)
    at node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:440:38
    at ReactShallowRenderer.render (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:412:39)
    at node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:444:37
    at withSetStateAllowed (node_modules/enzyme-adapter-utils/build/Utils.js:137:16)
    at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:443:70)
    at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:206:22)
    at Object.shallow (node_modules/enzyme/build/shallow.js:21:10)
    at Object.<anonymous> (src/Test.test.tsx:6:18)
        at new Promise (<anonymous>)
    at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
    at process._tickCallback (internal/process/next_tick.js:68:7)

Expected behavior

Tests should run

Your environment

Fresh create-react-app-typescript install with react 16.7.0-alpha-0

API

  • shallow
  • mount
  • render

Version

library version
enzyme 3.8.0
react 16.7.0-alpha.0
react-dom 16.7.0-alpha.0
react-test-renderer
adapter (below)

Adapter

  • enzyme-adapter-react-16
  • enzyme-adapter-react-16.3
  • enzyme-adapter-react-16.2
  • enzyme-adapter-react-16.1
  • enzyme-adapter-react-15
  • enzyme-adapter-react-15.4
  • enzyme-adapter-react-14
  • enzyme-adapter-react-13
  • enzyme-adapter-react-helper
  • others ( )
@ljharb
Copy link
Member

ljharb commented Dec 12, 2018

Hooks are not yet in a non-alpha version, and are definitely not yet supported in enzyme.

I'd suggest waiting to use them until they're not experimental.

@ljharb ljharb closed this as completed Dec 12, 2018
@oliviertassinari
Copy link

oliviertassinari commented Dec 15, 2018

@ljharb Hooks are labeled "upcoming" now 🤔.

@ljharb
Copy link
Member

ljharb commented Dec 15, 2018

Fair enough, but they’re still both not yet a thing in a real version, and enzyme doesn’t yet have support for them.

@juniusfree
Copy link

juniusfree commented Jan 8, 2019

An open issue in react facebook/react/#14091

@juniusfree
Copy link

juniusfree commented Jan 19, 2019

An open issue in react facebook/react/#14091

Merged at facebook/react#14567

@ljharb
Copy link
Member

ljharb commented Jan 24, 2019

Reopening, since facebook/react#14679 is now merged and v16.8 seems imminent.

Also tracked in #1553.

@ljharb ljharb reopened this Jan 24, 2019
@chenesan
Copy link
Contributor

@ljharb I'd love to help to look into what we need to do to support hooks after v16.8 release.
Also note that in facebook/react#14567 the shallow renderer will support hooks, so maybe there won't be too much work in enzyme.

@ljharb
Copy link
Member

ljharb commented Jan 24, 2019

@chenesan that would be great. The biggest priority is getting as many tests as possible, and mirroring those as much as possible between shallow and mount.

@joshnabbott
Copy link

Hooks have been released and this still seems to be an issue with enzyme's shallow renderer. Is there any sort of ETA on getting an update to enzyme to support hooks?

@chenesan
Copy link
Contributor

chenesan commented Feb 7, 2019

I don't know if there's any ETA for supporting hooks but I'll start to work on this in recent days :-)

@ljharb
Copy link
Member

ljharb commented Feb 7, 2019

If it’s still an issue with every React package (including the test renderer) at 16.8, then a PR with tests would be quite welcome.

@wodenx
Copy link

wodenx commented Feb 7, 2019

I am having a possibly related issue with 16.8. I don't get the Invariant Violation, but some hooks which persist data across renders (useRef and useState) are not behaving as expected during shallow render, but work fine during mount.

Versions:

├─ enzyme-adapter-react-16@1.9.1
├─ enzyme@3.8.0
├─ jest@23.6.0
├─ react-dom@16.8.1
└─ react@16.8.1

Tests:

import * as React from 'react';
import { shallow, mount } from 'enzyme';

class ClassInstance extends React.Component {
  constructor(props) {
    super(props);
    this.id = Math.random().toString();
  }

  render() {
    return <div>{this.id}</div>;
  }
}

const SFCRef = () => {
  const id = React.useRef(Math.random().toString());
  return <div>{id.current}</div>;
};

const SFCState = () => {
  const [id] = React.useState(Math.random().toString());
  return <div>{id}</div>;
};

test('1 class instance property persists with shallow', () => {
  const wrapper = shallow(<ClassInstance foo="a" />);
  const id = wrapper.text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.text();
  expect(id).toBe(id1);
});

test('2 class instance property persists with mount', () => {
  const wrapper = mount(<ClassInstance foo="a" />);
  const id = wrapper.find('div').text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.find('div').text();
  expect(id).toBe(id1);
});

test('3 SFC ref persists with mount', () => {
  const wrapper = mount(<SFCRef foo="a" />);
  const id = wrapper.find('div').text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.find('div').text();
  expect(id).toBe(id1);
});

test('4 SFC ref persists with shallow', () => {
  const wrapper = shallow(<SFCRef foo="a" />);
  const id = wrapper.text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.text();
  expect(id).toBe(id1);
});

test('5 SFC state persists with mount', () => {
  const wrapper = mount(<SFCState foo="a" />);
  const id = wrapper.find('div').text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.find('div').text();
  expect(id).toBe(id1);
});

test('6 SFC state persists with shallow', () => {
  const wrapper = shallow(<SFCState foo="a" />);
  const id = wrapper.text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.text();
  expect(id).toBe(id1);
});

// Verify that an id which *should not* persist across renders in fact does not.
const SFC = () => {
  const id = Math.random().toString();
  return <div>{id}</div>;
}

test('7 SFC alone does not persist with mount', () => {
  const wrapper = mount(<SFC foo="a" />);
  const id = wrapper.find('div').text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.find('div').text();
  expect(id).not.toBe(id1);
});

test('8 SFC alone does not persist with shallow', () => {
  const wrapper = shallow(<SFC foo="a" />);
  const id = wrapper.text();
  wrapper.setProps({ foo: 'b' });
  const id1 = wrapper.text();
  expect(id).not.toBe(id1);
});

Results:

  ✓ 1 class instance property persists with shallow (8ms)
  ✓ 2 class instance property persists with mount (31ms)
  ✓ 3 SFC ref persists with mount (3ms)
  ✕ 4 SFC ref persists with shallow (11ms)
  ✓ 5 SFC state persists with mount (2ms)
  ✕ 6 SFC state persists with shallow (2ms)
  ✓ 7 SFC alone does not persist with mount (3ms)
  ✓ 8 SFC alone does not persist with shallow

@ljharb
Copy link
Member

ljharb commented Feb 7, 2019

@wodenx what version of react-test-renderer do you have?

@wodenx
Copy link

wodenx commented Feb 7, 2019

@ljharb lol i was just checking that - i just upgraded from 16.8.0 to 16.8.1 and have the same results.

@icyJoseph
Copy link

Has anyone managed to pull out a magic trick to use Hooks, with Enzyme, shallow or mount?

@TdyP
Copy link
Author

TdyP commented Feb 12, 2019

@icyJoseph I managed to get this working by upgrading everything around react and testing:

"enzyme": "3.8.0"
"enzyme-adapter-react-16": "1.9.1",
"react": "16.8.1",
"react-dom": "16.8.1",
"react-test-renderer": "16.8.1",

@icyJoseph
Copy link

@TdyP I have all of those libraries updated, and still I cannot get a simple toggle to work.

In your example at the top, how do you trigger the state change when using the onClick prop?

I target the prop and call the function attached to it, using the act helper, but the state does not update.

@TdyP
Copy link
Author

TdyP commented Feb 12, 2019

@icyJoseph Here's a working example with mount. Didn't manage to get it working with shallowactually

The component Test.tsx

import * as React from 'react';

function Test() {
    const [myState, setMyState] = React.useState('initial_state');

    const changeState = () => setMyState('updated_state');

    return (
        <div>
            <div className="stateContent">{myState}</div>
            <button onClick={changeState}>Change</button>
        </div>
    );
}

export default Test;

Test file Test.test.tsx

import { mount } from 'enzyme';
import * as React from 'react';
import Test from './Test';

it('renders without crashing', () => {
    const comp = mount(<Test />);

    comp.find('button').simulate('click');

    expect(comp.find('div.stateContent').text()).toBe('updated_state');
});

Hope this helps!

@stringerbell
Copy link

This works for now. You need to add react-test-renderer if it's not already a dependency.


const ComponentWithHooks = () => {
  const [count, setCount] = useState(0);
  return <p onClick={() => setCount(count + 1)}>Count is {count}</p>;
};
describe('ComponentWithHooks', () => {
  it('explodes', () => {
    const wrapper = shallow(<ComponentWithHooks />); // Invariant Violation: Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)
  });

  it("doesn't explode with a workaround", () => {
    const renderer = new ShallowRenderer();
    renderer.render(<ComponentWithHooks />);
    const output = renderer.getRenderOutput();
    const wrapper = shallow(<div>{output}</div>); // Have to wrap it, otherwise you get: TypeError: ShallowWrapper can only wrap valid elements
    // ...
  });
});

@icyJoseph
Copy link

This works for now. You need to add react-test-renderer if it's not already a dependency.


const ComponentWithHooks = () => {
  const [count, setCount] = useState(0);
  return <p onClick={() => setCount(count + 1)}>Count is {count}</p>;
};
describe('ComponentWithHooks', () => {
  it('explodes', () => {
    const wrapper = shallow(<ComponentWithHooks />); // Invariant Violation: Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)
  });

  it("doesn't explode with a workaround", () => {
    const renderer = new ShallowRenderer();
    renderer.render(<ComponentWithHooks />);
    const output = renderer.getRenderOutput();
    const wrapper = shallow(<div>{output}</div>); // Have to wrap it, otherwise you get: TypeError: ShallowWrapper can only wrap valid elements
    // ...
  });
});

I think, the invariant problem goes away if you just change that arrow function in ComponentWithHooks for a regular JS function. The problem comes down the line in the testing because shallow rendered components do not update the state through the hooks. Only mount does that.

@stringerbell
Copy link

stringerbell commented Feb 15, 2019

@icyJoseph ah gotcha -- I misunderstood what was wanted based on the invariant violation issue in here (and what I was running into)

@bdwain
Copy link
Contributor

bdwain commented Mar 31, 2019

I created a react issue for this

@koszatnik12
Copy link

koszatnik12 commented Apr 3, 2019

@bdwain Hi :)
You wrote, that you've been able to run a simple test with useState, I've alse written one simple case and unfortunately it did not work. Maybe you know what is wrong here or maybe it is some React bugs - I am not sure, and I don't want to create new issue if it is only my mystake

   import React, { useState } from 'react';

    const TestComponent = () => {
        const [myState, setMyState] = useState('1234');

        return (
            <>
                <input onChange={({ target: { value } }) => setMyState(value)} />
                <p>{myState}</p>
            </>
        );
    };
    export { TestComponent };
    describe('Test Copoment', () => {
        it('should change myState value on input change', () => {
            // Given
            const testComponent = shallow(<TestComponent />);
            const { onChange } = testComponent.find('input').props();

            // Then 
            expect(testComponent.find('p')).toHaveText('1234');

            // When
            act(() => {
                onChange({ target: { value: '8658' } });
                components.update();
            });

            // Then 
            expect(testComponent.find('p')).toHaveText('8658');
        });
    });

// Text is still 1234 (initial state)

@palaniichukdmytro
Copy link

@koszatnik12 I have the same, with enzyme. I updated to lates 16.8.5 but simple unit test with useState does not work for me.

@ljharb
Copy link
Member

ljharb commented Apr 3, 2019

@koszatnik12 there’s a few unreleased changes that might address your problem, but also, there’s no components defined in your example. If you’re still having trouble after the next release, please file a new issue.

@alexanderkjeldaas
Copy link

@ljharb which release is that?

@ljharb
Copy link
Member

ljharb commented Apr 11, 2019

@alexanderkjeldaas the next one.

@kamyaD
Copy link

kamyaD commented Apr 13, 2019

Updating everything solved the problem for me:
"devDependencies": { "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.4.0", "eslint": "^5.16.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-test-renderer": "^16.6.2" }

@yuritoledo
Copy link

any update?

@0xdevalias
Copy link

0xdevalias commented Apr 24, 2019

I know it's been said a number of times above, but the main part of the suggested solutions for me was to force yarn to force the resolution of react-test-renderer to match my react version:

So I basically ended up with:

..snip..
"dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "enzyme": "^3.9.0",
    "enzyme-adapter-react-16": "^1.12.1",
},
"resolutions": {
    "react-test-renderer": "^16.8.6"
  }
}

@ljharb
Copy link
Member

ljharb commented Apr 24, 2019

you don’t even have to do that if your lockfile has the right one.

@jnelken
Copy link

jnelken commented Apr 30, 2019

You can follow the progress here (useEffect with shallow): #2086

@yuritoledo
Copy link

And about onChange with hooks?

@ljharb
Copy link
Member

ljharb commented Jun 4, 2019

v3.10.0 has now been released.

@EduardoAC
Copy link

EduardoAC commented Jun 5, 2019

I am having some trouble to test with shallow render the useState

{
  "jest": "24.8.0",
  "enzyme": "3.10.0",
  "enzyme-adapter-react-16": "1.14.0",
  "enzyme-to-json": "3.3.5",
}

File test is the same used by #1938 (comment)

Screenshot 2019-06-05 at 09 50 49

Happy to raise a new issue if this is not the right place

@ljharb
Copy link
Member

ljharb commented Jun 5, 2019

@EduardoAC a new issue would be very helpful, thanks!

@bob-lee
Copy link

bob-lee commented Jun 11, 2019

I had the same issue and the reason was multiple versions of react-test-renderer

$ yarn why react-test-renderer
yarn why v1.16.0
[1/4] Why do we have the module "react-test-renderer"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "react-test-renderer@16.8.6"
info Has been hoisted to "react-test-renderer"
info This module exists because it's specified in "dependencies".
=> Found "enzyme-adapter-react-16#react-test-renderer@16.2.0"
info This module exists because "enzyme-adapter-react-16" depends on it.
Done in 1.74s.

For some reason, enzyme-adapter-react-16 had an old react-test-renderer@16.2.0 as its own dependency.
I had to remove and add them back:

yarn remove enzyme enzyme-adapter-react-16 react-test-renderer
yarn add enzyme enzyme-adapter-react-16 react-test-renderer

@cagmz
Copy link

cagmz commented Aug 14, 2019

I was getting intermittent testing failures even after #1938 (comment) . I was able to get my specs passing consistently by doing the following:

yarn upgrade react react-dom --exact
yarn remove enzyme enzyme-adapter-react-16 react-test-renderer
yarn add enzyme enzyme-adapter-react-16 react-test-renderer

Then using Enzyme's mount (instead of shallow) in my failing specs (found after reading this post).

@lucasmonteiro001
Copy link

@icyJoseph I managed to get this working by upgrading everything around react and testing:

"enzyme": "3.8.0"
"enzyme-adapter-react-16": "1.9.1",
"react": "16.8.1",
"react-dom": "16.8.1",
"react-test-renderer": "16.8.1",

Thanks a lot @EduardoAC ! It did save me! 😄

@joselcc
Copy link

joselcc commented Oct 17, 2019

updating enzyme-adapter-react-16 to version 1.15.1 fixed the problem for me

@EduardoAC
Copy link

@lucasmonteiro001 you are very welcome

@joselcc great to hear it

@EduardoFLima
Copy link

@icyJoseph I managed to get this working by upgrading everything around react and testing:

"enzyme": "3.8.0"
"enzyme-adapter-react-16": "1.9.1",
"react": "16.8.1",
"react-dom": "16.8.1",
"react-test-renderer": "16.8.1",

That worked for me too, thanks !

@boda-sh
Copy link

boda-sh commented Feb 17, 2020

This solution worked for us, but we also realised we did not need react-test-renderer at all, so we removed it from package.json completely

yarn remove enzyme enzyme-adapter-react-16 react-test-renderer
yarn add enzyme enzyme-adapter-react-16

Test cases with Hooks still works

@ljharb
Copy link
Member

ljharb commented Feb 17, 2020

@bodazhao it comes along with the react 16 enzyme adapter anyways; but it’s version still needs to be the same minor as react and react-dom.

@danielo515
Copy link

This are my dependencies, and I still get this error (hook should not be called outside of balblabla)

    "react": "^16.14.0",
    "react-dom": "^16.13.1",
    "react-test-renderer": "16.14.0",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.2"

Does it matter that between my test file and the inner tested component there is a class based one?
SOmething like this

mount( <Provider><TestedClassComponent/></Provider>

Where TestedClassComponent is a class component that internally uses a functional component that depends on the provider? (which is also functional)

@danielo515
Copy link

Does it matter that between my test file and the inner tested component there is a class based one?

I just converted the middle component from class based to functional one and the problem persists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests