title: "Invalid hook call" when mocking React HOC with Jest
body: <p>I'm using <a href="https://github.com/FoundersFactory/react-speech-recognition" rel="noreferrer">react-speech-recognition</a> to transcribe speech to text in my React app. react-speech-recognition provides the <code>SpeechRecognition</code> higher-order component, which injects additional properties like <code>browserSupportsSpeechRecognition</code> into wrapped components.</p>
  
  <p>My App component looks like this:</p>
  
  <pre><code>// src/App.js
  import React, { useEffect } from 'react';
  import SpeechRecognition from 'react-speech-recognition';
  
  const App = ({ transcript, browserSupportsSpeechRecognition }) =&gt; {
      useEffect(() =&gt; {
          console.log(`transcript changed: ${transcript}`);
      }, [transcript]);
  
      if (! browserSupportsSpeechRecognition) {
          return &lt;span className="error"&gt;Speech recognition not supported&lt;/span&gt;;
      }
  
      return &lt;span className="transcript"&gt;{transcript}&lt;/span&gt;;
  };
  
  const options = {
      autoStart: false,
      continuous: false
  };
  
  export default SpeechRecognition(options)(App);
  </code></pre>
  
  <p>I wrote some tests to emulate both browsers that support speech recognition and browsers that don't:</p>
  
  <pre><code>// src/App.spec.js
  import React from 'react';
  import Enzyme, { mount } from 'enzyme';
  import Adapter from 'enzyme-adapter-react-16';
  import chai, { expect } from 'chai';
  import chaiEnzyme from 'chai-enzyme';
  
  chai.use(chaiEnzyme());
  
  Enzyme.configure({ adapter: new Adapter() });
  
  // Generate a mock SpeechRecognition HOC with the given props
  function mockSpeechRecognition(mockProps) {
      return function(options) {
          return function(WrappedComponent) {
              return function(props) {
                  return (
                      &lt;WrappedComponent 
                          {...props}
                          {...mockProps}
                          recognition={{}}
                      /&gt;
                  );
              };
          };
      };
  }
  
  describe('App component', () =&gt; {
  
      beforeEach(() =&gt; jest.resetModules());
  
      it('should show an error when speech recognition is not supported', () =&gt; {
          jest.mock('react-speech-recognition', () =&gt; mockSpeechRecognition({
              browserSupportsSpeechRecognition: false
          }));
  
          const App = require('./App').default;
          const wrapper = mount(&lt;App /&gt;);
  
          expect(wrapper).to.contain.exactly(1).descendants('.error');
          expect(wrapper.find('.error'))
              .to.have.text('Speech recognition not supported');
      });
  
      it('should show the transcript when speech recognition is supported', () =&gt; {
          jest.mock('react-speech-recognition', () =&gt; mockSpeechRecognition({
              browserSupportsSpeechRecognition: true,
              transcript: 'foo'
          }));
  
          const App = require('./App').default;
          const wrapper = mount(&lt;App /&gt;);
  
          expect(wrapper).to.contain.exactly(1).descendants('.transcript');
          expect(wrapper.find('.transcript')).to.have.text('foo');
      });
  
  });
  </code></pre>
  
  <p>When I run these tests, I get an <a href="https://reactjs.org/warnings/invalid-hook-call-warning.html" rel="noreferrer">"Invalid hook call" error</a> that causes the tests to fail:</p>
  
  <pre><code>  ● App component › should show an error when speech recognition is not supported
  
      Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
      1. You might have mismatching versions of React and the renderer (such as React DOM)
      2. You might be breaking the Rules of Hooks
      3. You might have more than one copy of React in the same app
      See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
  
        3 | 
        4 | const App = ({ transcript, browserSupportsSpeechRecognition }) =&gt; {
      &gt; 5 |     useEffect(() =&gt; {
          |     ^
        6 |         console.log(`transcript changed: ${transcript}`);
        7 |     }, [transcript]);
        8 | 
  
        at resolveDispatcher (node_modules/react/cjs/react.development.js:1465:13)
        at useEffect (node_modules/react/cjs/react.development.js:1508:20)
        at App (src/App.js:5:5)
        at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14803:18)
        at mountIndeterminateComponent (node_modules/react-dom/cjs/react-dom.development.js:17482:13)
        at beginWork (node_modules/react-dom/cjs/react-dom.development.js:18596:16)
        at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:188:14)
        at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
        at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
        at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
        at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
        at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
        at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:22157:12)
        at workLoopSync (node_modules/react-dom/cjs/react-dom.development.js:22130:22)
        at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:21756:9)
        at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:21188:7)
        at updateContainer (node_modules/react-dom/cjs/react-dom.development.js:24373:3)
        at node_modules/react-dom/cjs/react-dom.development.js:24758:7
        at unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:21903:12)
        at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:24757:5)
        at Object.render (node_modules/react-dom/cjs/react-dom.development.js:24840:10)
        at fn (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:437:26)
        at node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:37
        at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:21856:12)
        at Object.act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:929:14)
        at wrapAct (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:13)
        at Object.render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:423:16)
        at new ReactWrapper (node_modules/enzyme/src/ReactWrapper.js:115:16)
        at mount (node_modules/enzyme/src/mount.js:10:10)
        at Object.&lt;anonymous&gt; (src/App.spec.js:38:25)
  
    ● App component › should show the transcript when speech recognition is supported
  
      Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
      1. You might have mismatching versions of React and the renderer (such as React DOM)
      2. You might be breaking the Rules of Hooks
      3. You might have more than one copy of React in the same app
      See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
  
        3 | 
        4 | const App = ({ transcript, browserSupportsSpeechRecognition }) =&gt; {
      &gt; 5 |     useEffect(() =&gt; {
          |     ^
        6 |         console.log(`transcript changed: ${transcript}`);
        7 |     }, [transcript]);
        8 | 
  
        at resolveDispatcher (node_modules/react/cjs/react.development.js:1465:13)
        at useEffect (node_modules/react/cjs/react.development.js:1508:20)
        at App (src/App.js:5:5)
        at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14803:18)
        at mountIndeterminateComponent (node_modules/react-dom/cjs/react-dom.development.js:17482:13)
        at beginWork (node_modules/react-dom/cjs/react-dom.development.js:18596:16)
        at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:188:14)
        at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
        at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
        at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
        at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
        at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
        at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:22157:12)
        at workLoopSync (node_modules/react-dom/cjs/react-dom.development.js:22130:22)
        at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:21756:9)
        at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:21188:7)
        at updateContainer (node_modules/react-dom/cjs/react-dom.development.js:24373:3)
        at node_modules/react-dom/cjs/react-dom.development.js:24758:7
        at unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:21903:12)
        at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:24757:5)
        at Object.render (node_modules/react-dom/cjs/react-dom.development.js:24840:10)
        at fn (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:437:26)
        at node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:37
        at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:21856:12)
        at Object.act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:929:14)
        at wrapAct (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:13)
        at Object.render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:423:16)
        at new ReactWrapper (node_modules/enzyme/src/ReactWrapper.js:115:16)
        at mount (node_modules/enzyme/src/mount.js:10:10)
        at Object.&lt;anonymous&gt; (src/App.spec.js:52:25)
  </code></pre>
  
  <p>However, there are no such errors when I run the dev server and view the page in a browser, and I can see the <code>useEffect</code> hook logging a message to the console. There are also no errors when I create a production build. I think the issue is in how I mocked out the <code>SpeechRecognition</code> HOC. The tests pass if I remove the <code>useEffect</code> hook.</p>
  
  <p>This is a brand new project started with create-react-app. I only have one copy of react and react-dom and the versions match:</p>
  
  <pre><code>$ npm ls react react-dom
  react-speech-recognition-invalid-hook-call@0.1.0 /Users/NMD/max_programming_projects/react-speech-recognition-invalid-hook-call
  ├── react@16.13.1 
  └── react-dom@16.13.1
  </code></pre>
  
  <p>How can I fix this error in my tests?</p>