“用Jest模拟React HOC时,无效的钩子调用”

问题描述 投票:0回答:2

我正在使用react-speech-recognition将语音转录为我的React应用程序中的文本。 react-speech-recognition提供SpeechRecognition高阶组件,它将诸如browserSupportsSpeechRecognition的其他属性注入到已包装的组件中。

我的应用组件看起来像这样:

// src/App.js
import React, { useEffect } from 'react';
import SpeechRecognition from 'react-speech-recognition';

const App = ({ transcript, browserSupportsSpeechRecognition }) => {
    useEffect(() => {
        console.log(`transcript changed: ${transcript}`);
    }, [transcript]);

    if (! browserSupportsSpeechRecognition) {
        return <span className="error">Speech recognition not supported</span>;
    }

    return <span className="transcript">{transcript}</span>;
};

const options = {
    autoStart: false,
    continuous: false
};

export default SpeechRecognition(options)(App);

我编写了一些测试来模拟支持语音识别的浏览器和不支持语音识别的浏览器:

// 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 (
                    <WrappedComponent 
                        {...props}
                        {...mockProps}
                        recognition={{}}
                    />
                );
            };
        };
    };
}

describe('App component', () => {

    beforeEach(() => jest.resetModules());

    it('should show an error when speech recognition is not supported', () => {
        jest.mock('react-speech-recognition', () => mockSpeechRecognition({
            browserSupportsSpeechRecognition: false
        }));

        const App = require('./App').default;
        const wrapper = mount(<App />);

        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', () => {
        jest.mock('react-speech-recognition', () => mockSpeechRecognition({
            browserSupportsSpeechRecognition: true,
            transcript: 'foo'
        }));

        const App = require('./App').default;
        const wrapper = mount(<App />);

        expect(wrapper).to.contain.exactly(1).descendants('.transcript');
        expect(wrapper.find('.transcript')).to.have.text('foo');
    });

});

[当我运行这些测试时,我得到一个"Invalid hook call" error,导致测试失败:

  ● 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 }) => {
    > 5 |     useEffect(() => {
        |     ^
      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.<anonymous> (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 }) => {
    > 5 |     useEffect(() => {
        |     ^
      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.<anonymous> (src/App.spec.js:52:25)

但是,当我运行开发服务器并在浏览器中查看页面时,没有此类错误,并且可以看到useEffect钩子将消息记录到控制台。创建生产版本时也没有错误。我认为问题在于我如何模拟SpeechRecognition HOC。如果删除useEffect钩子,则测试通过。

这是一个以create-react-app开始的全新项目。我只有一个react和react-dom副本,并且版本匹配:

$ npm ls react react-dom
[email protected] /Users/NMD/max_programming_projects/react-speech-recognition-invalid-hook-call
├── [email protected] 
└── [email protected]

如何在测试中解决此错误?

reactjs jestjs enzyme higher-order-components
2个回答
0
投票

您可以按照以下方式尝试模拟SpeechRecognition吗?

jest.mock('react-speech-recognition', () => ({
  __esModule: true, 
  default: mockSpeechRecognition({
    browserSupportsSpeechRecognition: false
  })
}));

0
投票

请参见enzyme github open issue

您几乎没有选择,但是通常,您需要通过传递browserSupportsSpeechRecognition来正确模拟您的语音识别,如下面的代码所示。

选项一

您可以嘲笑useEffect。只需编写代码来满足您在useEffect模拟中的需求。

describe("App component", () => {
  beforeEach(() => jest.resetModules());

  it("should show an error when speech recognition is not supported", () => {

    jest.mock("react", () => ({
      ...jest.requireActual("React"),
      useEffect: (f) => f(),
    }));

    jest.mock("react-speech-recognition", () => {
      return mockSpeechRecognition({ browserSupportsSpeechRecognition: false });
    });

    const App = require("./App").default;
    const wrapper = mount(<App transcript={"hi"} />);

    expect(wrapper).to.contain.exactly(1).descendants(".error");
    expect(wrapper.find(".error")).to.have.text(
      "Speech recognition not supported"
    );
  });

选项二

您实际上根本不需要嘲笑语音识别。这是开销。图书馆人员将自己进行测试。您可以命名导出App并将其导入并编写常规测试。

describe("App component - no mock", () => {
  beforeEach(() => jest.resetModules());

  it("should show an error when speech recognition is not supported", () => {
    const wrapper = mount(
      <App browserSupportsSpeechRecognition={false} transcript={"hi"} />
    );

    expect(wrapper).to.contain.exactly(1).descendants(".error");
    expect(wrapper.find(".error")).to.have.text(
      "Speech recognition not supported"
    );
  });

  it("should NOT show an error when speech recognition is not supported", () => {
    const wrapper = mount(
      <App browserSupportsSpeechRecognition={true} transcript={"hi"} />
    );

    expect(wrapper).does.not.contain.descendants(".error");
    // expect(wrapper.find(".error")).to.have.text(
    //   "Speech recognition not supported"
    // );
  });
});

选项三

使用反应测试库代替酶。

参考:

© www.soinside.com 2019 - 2024. All rights reserved.