简体   繁体   English

如何在 jest 和 enzyme 中为 useState Hook 设置初始 state?

[英]How to set initial state for useState Hook in jest and enzyme?

Currently Im using functional component with react hooks.目前我正在使用带有反应挂钩的功能组件。 But I'm unable to test the useState hook completely.但是我无法完全测试useState挂钩。 Consider a scenario like, in useEffect hook I'm doing an API call and setting value in the useState .考虑这样的场景,在useEffect挂钩中,我正在调用 API 并在useState中设置值。 For jest/enzyme I have mocked data to test but I'm unable to set initial state value for useState in jest.对于玩笑/酶,我模拟了要测试的数据,但我无法在玩笑中为useState设置初始 state 值。

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

I want to set initial state as array of object in jest.我想开玩笑地将初始 state 设置为 object 的数组。 I could not find any setState function as similar like class component.我找不到任何 setState function 与 class 组件类似。

You can mock React.useState to return a different initial state in your tests:你可以模拟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))

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

First, you cannot use destructuring in your component.首先,您不能在组件中使用解构。 For example, you cannot use:例如,您不能使用:

import React, { useState } from 'react';
const [myState, setMyState] = useState();

Instead, you have to use:相反,您必须使用:

import React from 'react'
const [myState, setMyState] = React.useState();

Then in your test.js file:然后在你的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 
}

If you use useState hook multiple times in your component:如果您在组件中多次使用 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.

If I recall correctly, you should try to avoid mocking out the built-in hooks like useState and useEffect .如果我useState useEffect话,你应该尽量避免模拟像useStateuseEffect这样的内置钩子。 If it is difficult to trigger the state change using enzyme's invoke() , then that may be an indicator that your component would benefit from being broken up.如果使用酶的invoke()很难触发状态更改,那么这可能表明您的组件将从分解中受益。

  • Below function will return state下面的函数将返回状态
const setHookState = (newState) => jest.fn().mockImplementation(() => [ newState, () => {}, ]);
  • Add below to use react添加以下以使用反应

const reactMock = require('react');

In your code, you must use React.useState() to this work, else it won't work在你的代码中,你必须使用React.useState()来完成这项工作,否则它将无法工作

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

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

  • Then in your test add following, mock state values然后在您的测试中添加以下模拟状态值

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

Inspiration: Goto灵感: 转到

SOLUTION WITH DE-STRUCTURING解构解决方案

You don't need to use React.useState - you can still destructure in your component.你不需要使用React.useState - 你仍然可以在你的组件中解构。

But you need to write your tests in accordance to the order in which your useState calls are made.但是您需要按照进行 useState 调用的顺序编写测试。 For example, if you want to mock two useState calls, make sure they're the first two useState calls in your component.例如,如果要模拟两个 useState 调用,请确保它们是组件中的前两个 useState 调用。

In your component:在您的组件中:

import React, { useState } from 'react';

const [firstOne, setFirstOne] = useState('');
const [secondOne, setSecondOne] = useState('');

In your test:在您的测试中:

import React from 'react';

jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [firstInitialState, () => null])
.mockImplementationOnce(() => [secondInitialState, () => null])
.mockImplementation((x) => [x, () => null]); // ensures that the rest are unaffected
//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);
});

I have spent a lot of time but found good solution for testing multiple useState in my app.我花了很多时间,但找到了在我的应用程序中测试多个 useState 的好解决方案。

export const setHookTestState = (newState: any) => {
  const setStateMockFn = () => {};
  return Object.keys(newState).reduce((acc, val) => {
    acc = acc?.mockImplementationOnce(() => [newState[val], setStateMockFn]);
    return acc;
  }, jest.fn());
};

where newState is object with state fields in my component;其中 newState 是我的组件中具有状态字段的对象;

for example:例如:

React.useState = setHookTestState({
      dataFilter: { startDate: '', endDate: '', today: true },
      usersStatisticData: [],
    });

NOT CHANGING TO React.useState不更改React.useState

This approach worked for me:这种方法对我有用:

 //import useState with alias just to know is a mock import React, { useState as useStateMock } from 'react' //preseve react as it actually is but useState jest.mock('react', () => ({...jest.requireActual('react'), useState: jest.fn(), })) describe('SearchBar', () => { const realUseState: any = useStateMock //create a ref copy (just for TS so it prevents errors) const setState = jest.fn() //this is optional, you can place jest.fn directly beforeEach(() => { realUseState.mockImplementation((init) => [init, setState]) //important, let u change the value of useState hook }) it('it should execute setGuestPickerFocused with true given that dates are entered', async () => { jest.spyOn(React, 'useState').mockImplementationOnce(() => ['', () => null]) //place the values in the order of your useStates.mockImplementationOnce(() => ['20220821', () => null]) //....mockImplementationOnce(() => ['20220827', () => null]) //... jest.spyOn(uiState, 'setGuestPickerFocused').mockReturnValue('') getRenderedComponent() expect(uiState.setGuestPickerFocused).toHaveBeenCalledWith(true) }) })

My component我的组件

 const MyComp: React.FC<MyCompProps> = ({ a, b, c, }) => { const [searchQuery, setSearchQuery] = useState('') // my first value const [startDate, setStartDate] = useState('') // my second value const [endDate, setEndDate] = useState('') // my third value useEffect(() => { console.log(searchQuery, startDate, endDate) // just to verifiy }, [])

Hope this helps希望这可以帮助

I used for multiple useState() Jest mocks the following setup in the component file我用于多个useState() Jest 模拟组件文件中的以下设置

const [isLoading, setLoading] = React.useState(false);
const [isError, setError] = React.useState(false);

Please note the useState mock will just work with React.useState() derivation.请注意useState模拟仅适用于React.useState()派生。

..and in the test.js ..在 test.js 中

describe('User interactions at error state changes', () => {

const setStateMock = jest.fn();
beforeEach(() => {
    const useStateMock = (useState) => [useState, setStateMock];
    React.useState.mockImplementation(useStateMock) 
    jest.spyOn(React, 'useState')
    .mockImplementationOnce(() => [false, () => null]) // this is first useState in the component
    .mockImplementationOnce(() => [true, () => null]) // this is second useState in the component
});

it('Verify on list the state error is visible', async () => {
    render(<TodoList />);
    ....

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM