Как установить начальное состояние для использования State Hook в шутке и энзиме?

В настоящее время я использую функциональный компонент с реагирующими крючками. Но я не смог полностью проверить useState Hook. Рассмотрим сценарий, например, в ловушке useEffect я делаю вызов API и устанавливаю значение в useState. Для jest / энзима у меня есть фиктивные данные для тестирования, но я не могу установить начальное значение состояния для useState в jest.

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

Я хочу установить начальное состояние как массив объекта в шутку. Я не смог найти функцию setState, похожую на компонент класса.

10 ответов

Вы можете издеваться 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

Во-первых, вы не можете использовать деструктурирование в своем компоненте. Например, нельзя использовать:

      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.

Если я правильно помню, вы должны стараться не издеваться над встроенными крючками, такими как useState а также useEffect, Если трудно вызвать изменение состояния с помощью фермента invoke()тогда это может быть индикатором того, что ваш компонент выиграет от разрыва.

РЕШЕНИЕ С ДЕСТРУКТУРИРОВАНИЕМ

Вам не нужно использовать 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
  • Ниже функция вернет состояние
const setHookState = (newState) => jest.fn().mockImplementation(() => [
  newState,
  () => {},
]);
  • Добавьте ниже, чтобы использовать реакцию

const reactMock = require('react');

В своем коде вы должны использовать React.useState() к этой работе, иначе это не сработает

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

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

  • Затем в вашем тесте добавьте следующие фиктивные значения состояния

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

Вдохновение: Goto

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

Я использовал для несколькихuseState()Jest издевается над следующей настройкой в ​​файле компонента

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

Обратите внимание наuseStatemock будет работать только с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 />);
    ....

Я потратил много времени, но нашел хорошее решение для тестирования нескольких 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: [],
    });

вот как вы можете легко это сделать без фермента. И вы даже можете сделать это, если используетеContext.

МойКомпонент.js

      const [comments, setComments] = useState();

МойКомпонент.test.js

      const comments = [{id:1, title: "first comment", body: "bla bla"}]
jest.spyOn(React, 'useState').mockReturnValueOnce([comments, jest.fn()]);
const { debug } = render(<MyComponent />);
debug();

Последние две строки кода предназначены для того, чтобы увидеть, как будет выглядеть DOM, как будет выглядеть состояние ваших комментариев при рендеринге.

НЕ МЕНЯЕТСЯ НА React.useState

Этот подход работал для меня:

Надеюсь это поможет!

Другие вопросы по тегам