如何在笑话和酶中为 useState Hook 设置初始状态?

Sri*_*gar 28 javascript reactjs enzyme react-hooks react-hooks-testing-library

目前我使用带有反应钩子的功能组件。但我无法useState完全测试钩子。考虑这样一个场景,在useEffect钩子中,我正在执行 API 调用并在useState. 对于玩笑/酶,我模拟了要测试的数据,但我无法useState在玩笑中设置初始状态值。

const [state, setState] = useState([]);

我想在玩笑中将初始状态设置为对象数组。我找不到任何类似于类组件的 setState 函数。

Jim*_*mmy 20

您可以模拟React.useState以在测试中返回不同的初始状态:

// Cache original functionality
const realUseState = React.useState

// Stub the initial state
const stubInitialState = ['stub data']

// Mock useState before rendering your component
jest
  .spyOn(React, 'useState')
  .mockImplementationOnce(() => realUseState(stubInitialState))
Run Code Online (Sandbox Code Playgroud)

参考: https : //dev.to/theactualgivens/testing-react-hook-state-changes-2oga

  • 如果组件中有多个 useState 语句怎么办? (12认同)
  • 如果您不知道 useState 的调用顺序怎么办?我有一个复杂的应用程序,有很多 useStates。我现在可能不应该测试实现。 (4认同)
  • 通过一系列的mockImplementationOnce()模拟多个useState。请参阅 https://jestjs.io/docs/en/mock-functions.html#mock-return-values (3认同)

Si *_*Thu 14

首先,您不能在组件中使用解构。例如,您不能使用:

import React, { useState } from 'react';
const [myState, setMyState] = useState();
Run Code Online (Sandbox Code Playgroud)

相反,您必须使用:

import React from 'react'
const [myState, setMyState] = React.useState();
Run Code Online (Sandbox Code Playgroud)

然后在您的test.js文件中:

test('useState mock', () => {
   const myInitialState = 'My Initial State'

   React.useState = jest.fn().mockReturnValue([myInitialState, {}])
   
   const wrapper = shallow(<MyComponent />)

   // initial state is set and you can now test your component 
}
Run Code Online (Sandbox Code Playgroud)

如果您在组件中多次使用 useState 钩子:

// in MyComponent.js

import React from 'react'

const [myFirstState, setMyFirstState] = React.useState();
const [mySecondState, setMySecondState] = React.useState();

// in MyComponent.test.js

test('useState mock', () => {
   const initialStateForFirstUseStateCall = 'My First Initial State'
   const initialStateForSecondUseStateCall = 'My Second Initial State'

   React.useState = jest.fn()
     .mockReturnValueOnce([initialStateForFirstUseStateCall, {}])
     .mockReturnValueOnce([initialStateForSecondUseStateCall, {}])
   
   const wrapper = shallow(<MyComponent />)

   // initial states are set and you can now test your component 
}
// actually testing of many `useEffect` calls sequentially as shown
// above makes your test fragile. I would recommend to use 
// `useReducer` instead.
Run Code Online (Sandbox Code Playgroud)

  • 您能详细说明一下为什么我们不能使用解构吗?因为据我所知,它是 React 组件的合法代码。 (6认同)
  • 这能达到什么目的?`useState` 是测试不应该知道或关心的实现细节。使用 props 并像用户一样与 UI 交互,将组件的状态间接设置为黑盒。事实上,您建议避免解构(这是一种常见的 React 习惯用法),纯粹是为了帮助注入模拟,这是一个巨大的危险信号,表明测试对组件的内部了解太多。 (3认同)

小智 9

如果我没记错的话,您应该尽量避免模拟useStateuseEffect. 如果使用酶的 很难触发状态变化invoke(),那么这可能表明您的组件将从分解中受益。

  • 确切地。通过 UI 与组件交互和/或模拟外部 API 响应来触发状态更改,而不是模拟状态。挂钩是实现细节。 (2认同)

小智 9

解构解决方案

您不需要使用React.useState- 您仍然可以在组件中解构。

但是您需要按照 useState 调用的顺序编写测试。例如,如果您想模拟两个 useState 调用,请确保它们是组件中的前两个 useState 调用。

在您的组件中:

import React, { useState } from 'react';

const [firstOne, setFirstOne] = useState('');
const [secondOne, setSecondOne] = useState('');
Run Code Online (Sandbox Code Playgroud)

在你的测试中:

import React from 'react';

jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [firstInitialState, () => null])
.mockImplementationOnce(() => [secondInitialState, () => null])
.mockImplementation((x) => [x, () => null]); // ensures that the rest are unaffected
Run Code Online (Sandbox Code Playgroud)

  • 如果我们只需要嘲笑最后一个呢?我们需要嘲笑他们所有人吗? (3认同)

Die*_*íaz 6

//Component    
const MyComponent = ({ someColl, someId }) => {
     const [myState, setMyState] = useState(null);

     useEffect(() => {loop every time group is set
         if (groupId) {
             const runEffect = async () => {
                  const data = someColl.find(s => s.id = someId);
                  setMyState(data);
             };
             runEffect();
         }
     }, [someId, someColl]);

     return (<div>{myState.name}</div>);
};

// Test
// Mock
const mockSetState = jest.fn();
jest.mock('react', () => ({
    ...jest.requireActual('react'),
    useState: initial => [initial, mockSetState]
}));
const coll = [{id: 1, name:'Test'}, {id: 2, name:'Test2'}];

it('renders correctly with groupId', () => {
    const wrapper = shallow(
        <MyComponent comeId={1} someColl={coll} />
    );
    setTimeout(() => {
        expect(wrapper).toMatchSnapshot();
        expect(mockSetState).toHaveBeenCalledWith({ id: 1, name: 'Test' });
    }, 100);
});
Run Code Online (Sandbox Code Playgroud)


小智 5

  • 下面的函数将返回状态
const setHookState = (newState) => jest.fn().mockImplementation(() => [
  newState,
  () => {},
]);
Run Code Online (Sandbox Code Playgroud)
  • 添加以下以使用反应

const reactMock = require('react');

在你的代码中,你必须习惯React.useState()这个工作,否则它不会工作

const [arrayValues, setArrayValues] = React.useState();

const [isFetching, setFetching] = React.useState();

  • 然后在您的测试中添加以下模拟状态值

reactMock.useState = setHookState({ arrayValues: [], isFetching: false, });

灵感:转到

  • 对我有用,谢谢。我还使用“import React, { useState } from 'react';”,因此您可以通过这种方法自由使用“useState”的扩展版本。 (2认同)