javascript 如何设置useState的初始状态Hook in jest和enzyme?

iqjalb3h  于 2023-01-24  发布在  Java
关注(0)|答案(9)|浏览(89)

目前我正在使用带有react钩子的函数组件。但是我无法完全测试useState钩子。考虑这样一个场景,在useEffect钩子中,我正在做一个API调用,并在useState中设置值。对于jest/enzyme,我模拟了要测试的数据,但是我无法在jest中设置useState的初始状态值。
const [state, setState] = useState([]);
我想在jest中将初始状态设置为对象数组。我找不到任何类似类组件的setState函数。

dz6r00yl

dz6r00yl1#

您可以模拟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))

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

dkqlctbz

dkqlctbz2#

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

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

相反,您必须用途:

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

然后在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 
}

如果在组件中多次使用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.
hmmo2u0o

hmmo2u0o3#

如果我没记错的话,您应该尽量避免使用内置的钩子,如useStateuseEffect,如果使用enzyme的invoke()很难触发状态改变,那么这可能表明您的组件将从分解中获益。

fivyi3re

fivyi3re4#

解结构

你不需要使用React.useState--你仍然可以在你的组件中解构。
但是你需要按照useState调用的顺序来编写测试,例如,如果你想模拟两个useState调用,确保它们是你组件中的前两个useState调用。
在您的组件中:

import React, { useState } from 'react';

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

在您的测试中:

import React from 'react';

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

ao218c7q5#

  • 以下函数将返回状态
const setHookState = (newState) =>
  jest.fn().mockImplementation(() => [
    newState,
    () => {},
  ]);

添加以下内容以使用react

const reactMock = require('react');

在您的代码中,必须使用React.useState()来完成此工作,否则它将无法工作

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

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

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

灵感:Goto

qyswt5oh

qyswt5oh6#

//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);
});
cygmwpex

cygmwpex7#

我花了很多时间,但找到了很好的解决方案,测试多个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());
};

其中newState是我的组件中具有状态字段的对象;
例如:

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

vlurs2pr8#

我在组件文件中为多个useState() Jest模拟使用了以下设置

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

请注意,useState模拟只适用于React.useState()派生。
..在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 />);
    ....
z0qdvdin

z0qdvdin9#

    • 不更改为React.useState**

这种方法对我很有效:

//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)
  })
})

我的组件

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
  }, [])

希望这有帮助!

相关问题